From 8a4909e1954c41d5fd316254a96457bcd53a8192 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:08:32 -0400 Subject: [PATCH 01/16] fix(search): follow daily-note settings in orphan defaults --- .env.example | 8 +- AGENTS.md | 1 + ARCHITECTURE.md | 2 +- DEPLOY.md | 2 +- DOCKERHUB.md | 2 +- README.md | 2 +- cli/src/env.ts | 16 +- deploy/local/.env.example | 8 +- deploy/remote/.env.example | 8 +- docker-compose.yml | 4 +- src/vault-mcp/__tests__/config.test.ts | 26 +- src/vault-mcp/config.ts | 13 +- .../__snapshots__/tool-surface/default.json | 4 +- .../tool-surface/disabled-tools.json | 4 +- .../tool-surface/embedding-off.json | 4 +- .../file-tools-off+embedding-off.json | 4 +- .../tool-surface/file-tools-off.json | 4 +- .../memory-off+embedding-off.json | 4 +- ...mory-off+file-tools-off+embedding-off.json | 4 +- .../memory-off+file-tools-off.json | 4 +- .../tool-surface/memory-off.json | 4 +- .../tool-surface/obsidian-sync.json | 4 +- .../tool-surface/readonly+embedding-off.json | 4 +- ...readonly+file-tools-off+embedding-off.json | 4 +- .../tool-surface/readonly+file-tools-off.json | 4 +- .../readonly+memory-off+embedding-off.json | 4 +- ...mory-off+file-tools-off+embedding-off.json | 4 +- .../readonly+memory-off+file-tools-off.json | 4 +- .../tool-surface/readonly+memory-off.json | 4 +- .../__snapshots__/tool-surface/readonly.json | 4 +- .../__tests__/tool-definitions.test.ts | 243 +++++++++++++++++- .../vault-orientation-prompt.test.ts | 189 ++++++++++++++ .../prompts/vault-orientation-prompt.ts | 23 +- .../tools/__tests__/vault-crud-tools.test.ts | 111 -------- src/vault-mcp/mcp-core/tools/search-tools.ts | 20 +- .../mcp-core/tools/vault-crud-tools.ts | 29 +-- .../__tests__/vault-folder-config.test.ts | 202 +++++++++++++++ .../vault-operations/vault-folder-config.ts | 71 +++++ 38 files changed, 819 insertions(+), 233 deletions(-) delete mode 100644 src/vault-mcp/mcp-core/tools/__tests__/vault-crud-tools.test.ts create mode 100644 src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts create mode 100644 src/vault-mcp/vault-operations/vault-folder-config.ts diff --git a/.env.example b/.env.example index 35356cb4e..6818e9913 100644 --- a/.env.example +++ b/.env.example @@ -232,9 +232,11 @@ GHCR_USER= # (default "Daily Notes"). When set, replaces the whole default — include the # daily notes folder in your list if needed. # PROTECTED_PATHS=About Me,Daily Notes -# Comma-separated folders excluded from orphan detection (default: the daily -# notes folder — DAILY_NOTES_FOLDER when set, otherwise "Daily Notes" — plus -# Templates and MEMORY_DIR). +# Comma-separated folders excluded from orphan detection. +# Default: the daily notes folder, Templates, and MEMORY_DIR. +# The daily folder is read on each query from DAILY_NOTES_FOLDER or +# .obsidian/daily-notes.json, falling back to "Daily Notes". +# When set, replaces the whole default — include every folder to keep excluded. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata (default: https://github.com/aliasunder/vault-cortex) # SERVICE_DOCUMENTATION_URL=https://github.com/youruser/your-fork diff --git a/AGENTS.md b/AGENTS.md index 671f6aa91..b230f37aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -167,6 +167,7 @@ src/ note-mover.ts # Move/rename a note + rewrite every vault-wide link to it memory-store.ts # About Me/ heading-aware read/append/delete daily-notes.ts # Daily note config reader + path resolver (env settings > daily-notes.json) + vault-folder-config.ts # Protected folders + live orphan exclusion defaults task-mutations.ts # Task create + state mutations (status, priority, heading moves, sub-tasks) task-format-config.ts # Tasks-plugin format config reader (emoji vs Dataview) + status registry trash-config.ts # Obsidian "Deleted files" config reader (trashOption from .obsidian/app.json) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 3c78f804a..92e1aa405 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -288,7 +288,7 @@ Link queries use a `links` table populated during indexing: 3. Basename (shortest-path-first for ambiguous basenames) - **Non-markdown files:** Targets that don't resolve to a note are checked against a `non_md_files` table (populated during rebuild, maintained by the file watcher). Both wikilinks and markdown-style links to `.canvas`, `.base`, images, PDFs, and other non-markdown files resolve as `kind: "file"` instead of being counted as broken. - **Outgoing links:** `vault_get_outgoing_links` returns a `kind` discriminator (`"note"` or `"file"`) plus each target's byte size (`bytes` — from the notes table for notes, from `non_md_files` for files), so clients can route notes to `vault_read_note` and files to `vault_read_file` with size awareness. -- **Orphans:** `vault_find_orphans` excludes folders listed in `ORPHAN_EXCLUDE_FOLDERS` (default: the daily notes folder — `DAILY_NOTES_FOLDER` or `Daily Notes` — plus `Templates` and the memory dir). +- **Orphans:** `vault_find_orphans` and `vault-orientation` resolve default exclusions on each invocation: the daily notes folder (`DAILY_NOTES_FOLDER` → `.obsidian/daily-notes.json` → `Daily Notes`), `Templates`, and the memory dir. `ORPHAN_EXCLUDE_FOLDERS` replaces that list; a tool call's `exclude_folders` replaces it for that call. ### Files diff --git a/DEPLOY.md b/DEPLOY.md index 65027f551..505dafd24 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -365,7 +365,7 @@ To find your stage: `cat .sst/stage` (after one-time setup). | `DISABLED_TOOLS` | Optional. Hide individual tools by name, comma-separated. Names match the Tool column in the [README tools table](./README.md#tools). Subtractive only; an unknown tool name stops the server at startup. Default: none hidden. | | `MEMORY_DIR` | Optional. Memory folder name in the vault (default: `About Me`). See the [Configuration](./README.md#configuration) section. | | `PROTECTED_PATHS` | Optional. Comma-separated folders protected from deletion and moves (default: `MEMORY_DIR` plus the daily notes folder, read from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json`, default `Daily Notes`). Overrides the default entirely when set. | -| `ORPHAN_EXCLUDE_FOLDERS` | Optional. Comma-separated folders excluded from orphan detection (default: `DAILY_NOTES_FOLDER, Templates, MEMORY_DIR`). Overrides the default entirely when set. | +| `ORPHAN_EXCLUDE_FOLDERS` | Optional. Comma-separated folders excluded from orphan detection (default: the daily notes folder, `Templates`, and `MEMORY_DIR`). The daily notes folder is read on each query from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | | `SERVICE_DOCUMENTATION_URL` | Optional. URL in OAuth discovery metadata (default: `https://github.com/aliasunder/vault-cortex`). Set to your fork's URL. | | `SYNC_CONFIGS` | Optional. Obsidian settings categories synced to the server, comma-separated (default: `core-plugin-data,community-plugin-data` — daily-notes settings and the Tasks plugin's format). Set `none` to disable. See [Daily notes](./README.md#daily-notes). | | `SYNC_EXCLUDED_FOLDERS` | Optional. Folders to leave out of Obsidian Sync, comma-separated — the same list as Obsidian's Sync → Excluded folders. Unset syncs everything. | diff --git a/DOCKERHUB.md b/DOCKERHUB.md index 0bc26371c..79d2416fc 100644 --- a/DOCKERHUB.md +++ b/DOCKERHUB.md @@ -167,7 +167,7 @@ All settings are environment variables with sensible defaults. Some defaults der | `DISABLED_TOOLS` | — | — | Hide individual tools by name, comma-separated (e.g. `vault_delete_note,vault_move_note`). Names match the Tool column in the [tools table](https://github.com/aliasunder/vault-cortex#tools). Subtractive only — it cannot re-enable a tool another setting hides. An unknown tool name stops the server at startup, so typos surface immediately. | | `MEMORY_DIR` | — | `About Me` | Vault folder for structured memory files | | `PROTECTED_PATHS` | — | `MEMORY_DIR`, daily notes folder | Folders that `vault_delete_note` and `vault_move_note` refuse to touch. The default daily notes folder is read from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | -| `ORPHAN_EXCLUDE_FOLDERS` | — | `DAILY_NOTES_FOLDER, Templates, MEMORY_DIR` | Folders excluded from orphan detection. The daily-notes part of the default comes from `DAILY_NOTES_FOLDER` only — this one doesn't read `daily-notes.json`. | +| `ORPHAN_EXCLUDE_FOLDERS` | — | daily notes folder, `Templates`, `MEMORY_DIR` | Folders excluded from orphan detection. The default daily notes folder is read on each query from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | | `DAILY_NOTES_FOLDER` | — | from vault config | Sets the folder your daily notes live in. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `Daily Notes`. See [Daily notes](https://github.com/aliasunder/vault-cortex#daily-notes). | | `DAILY_NOTES_FORMAT` | — | from vault config | Sets the daily note filename format — same tokens as Obsidian's daily note date format setting. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `YYYY-MM-DD`. See [Daily notes](https://github.com/aliasunder/vault-cortex#daily-notes). | | `TZ` | — | `UTC` | IANA timezone for timestamps and daily note resolution | diff --git a/README.md b/README.md index 2226594ce..39e89abe9 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ All settings are environment variables with sensible defaults. Some defaults der | `DISABLED_TOOLS` | — | — | Hide individual tools by name, comma-separated (e.g. `vault_delete_note,vault_move_note`). Names match the Tool column in the [tools table](#tools). Subtractive only — it cannot re-enable a tool another setting hides. An unknown tool name stops the server at startup, so typos surface immediately. | | `MEMORY_DIR` | — | `About Me` | Vault folder for structured memory files | | `PROTECTED_PATHS` | — | `MEMORY_DIR`, daily notes folder | Folders that `vault_delete_note` and `vault_move_note` refuse to touch. The default daily notes folder is read from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | -| `ORPHAN_EXCLUDE_FOLDERS` | — | `DAILY_NOTES_FOLDER, Templates, MEMORY_DIR` | Folders excluded from orphan detection. The daily-notes part of the default comes from `DAILY_NOTES_FOLDER` only — this one doesn't read `daily-notes.json`. | +| `ORPHAN_EXCLUDE_FOLDERS` | — | daily notes folder, `Templates`, `MEMORY_DIR` | Folders excluded from orphan detection. The default daily notes folder is read on each query from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | | `DAILY_NOTES_FOLDER` | — | from vault config | Sets the folder your daily notes live in. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `Daily Notes`. See [Daily notes](#daily-notes). | | `DAILY_NOTES_FORMAT` | — | from vault config | Sets the daily note filename format — same tokens as Obsidian's daily note date format setting. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `YYYY-MM-DD`. See [Daily notes](#daily-notes). | | `TZ` | — | `UTC` | IANA timezone for timestamps and daily note resolution | diff --git a/cli/src/env.ts b/cli/src/env.ts index 87ff9608b..eb5fc0ea5 100644 --- a/cli/src/env.ts +++ b/cli/src/env.ts @@ -108,9 +108,11 @@ MEMORY_DIR=About Me # daily notes folder in your list if needed. # PROTECTED_PATHS=About Me,Daily Notes -# Comma-separated folders excluded from orphan detection (default: the daily -# notes folder — DAILY_NOTES_FOLDER when set, otherwise "Daily Notes" — plus -# Templates and MEMORY_DIR). +# Comma-separated folders excluded from orphan detection. +# Default: the daily notes folder, Templates, and MEMORY_DIR. +# The daily folder is read on each query from DAILY_NOTES_FOLDER or +# .obsidian/daily-notes.json, falling back to "Daily Notes". +# When set, replaces the whole default — include every folder to keep excluded. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata @@ -249,9 +251,11 @@ MEMORY_DIR=About Me # daily notes folder in your list if needed. # PROTECTED_PATHS=About Me,Daily Notes -# Comma-separated folders excluded from orphan detection (default: the daily -# notes folder — DAILY_NOTES_FOLDER when set, otherwise "Daily Notes" — plus -# Templates and MEMORY_DIR). +# Comma-separated folders excluded from orphan detection. +# Default: the daily notes folder, Templates, and MEMORY_DIR. +# The daily folder is read on each query from DAILY_NOTES_FOLDER or +# .obsidian/daily-notes.json, falling back to "Daily Notes". +# When set, replaces the whole default — include every folder to keep excluded. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata diff --git a/deploy/local/.env.example b/deploy/local/.env.example index 641b7652b..46aa4f86d 100644 --- a/deploy/local/.env.example +++ b/deploy/local/.env.example @@ -95,9 +95,11 @@ MEMORY_DIR=About Me # daily notes folder in your list if needed. # PROTECTED_PATHS=About Me,Daily Notes -# Comma-separated folders excluded from orphan detection (default: the daily -# notes folder — DAILY_NOTES_FOLDER when set, otherwise "Daily Notes" — plus -# Templates and MEMORY_DIR). +# Comma-separated folders excluded from orphan detection. +# Default: the daily notes folder, Templates, and MEMORY_DIR. +# The daily folder is read on each query from DAILY_NOTES_FOLDER or +# .obsidian/daily-notes.json, falling back to "Daily Notes". +# When set, replaces the whole default — include every folder to keep excluded. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata diff --git a/deploy/remote/.env.example b/deploy/remote/.env.example index 2aa5e688f..96bb5fbcc 100644 --- a/deploy/remote/.env.example +++ b/deploy/remote/.env.example @@ -102,9 +102,11 @@ MEMORY_DIR=About Me # daily notes folder in your list if needed. # PROTECTED_PATHS=About Me,Daily Notes -# Comma-separated folders excluded from orphan detection (default: the daily -# notes folder — DAILY_NOTES_FOLDER when set, otherwise "Daily Notes" — plus -# Templates and MEMORY_DIR). +# Comma-separated folders excluded from orphan detection. +# Default: the daily notes folder, Templates, and MEMORY_DIR. +# The daily folder is read on each query from DAILY_NOTES_FOLDER or +# .obsidian/daily-notes.json, falling back to "Daily Notes". +# When set, replaces the whole default — include every folder to keep excluded. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata diff --git a/docker-compose.yml b/docker-compose.yml index 1b80765d5..5c724a4ff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -99,8 +99,8 @@ services: MEMORY_DIR: ${MEMORY_DIR:-About Me} # Left empty = the server applies the smart defaults listed below, where and # stand for the resolved values (defaults "About Me" and "Daily Notes"). - # ORPHAN_EXCLUDE_FOLDERS resolves its daily-notes value from the DAILY_NOTES_FOLDER env var - # alone — it never reads daily-notes.json. + # The daily notes folder is resolved from DAILY_NOTES_FOLDER or + # .obsidian/daily-notes.json, falling back to "Daily Notes". # PROTECTED_PATHS default: ", " # ORPHAN_EXCLUDE_FOLDERS default: ", Templates, " # SERVICE_DOCUMENTATION_URL default: https://github.com/aliasunder/vault-cortex diff --git a/src/vault-mcp/__tests__/config.test.ts b/src/vault-mcp/__tests__/config.test.ts index 97bb96216..0e2e913b6 100644 --- a/src/vault-mcp/__tests__/config.test.ts +++ b/src/vault-mcp/__tests__/config.test.ts @@ -16,9 +16,9 @@ describe("loadConfig", () => { expect(config.protectedPathsOverride).toBeNull() }) - it("orphanExcludeFolders defaults to Daily Notes, Templates, About Me", () => { + it("orphanExcludeFoldersOverride is null when ORPHAN_EXCLUDE_FOLDERS is unset", () => { const config = loadConfig(EMPTY_ENV) - expect(config.orphanExcludeFolders).toEqual(["Daily Notes", "Templates", "About Me"]) + expect(config.orphanExcludeFoldersOverride).toBeNull() }) it("serviceDocumentationUrl defaults to the GitHub repo", () => { @@ -48,9 +48,9 @@ describe("loadConfig", () => { expect(config.memoryDir).toBe("Profile") }) - it("cascades into orphanExcludeFolders when ORPHAN_EXCLUDE_FOLDERS is not set", () => { + it("leaves the orphan override unset when ORPHAN_EXCLUDE_FOLDERS is not set", () => { const config = loadConfig({ MEMORY_DIR: "Profile" }) - expect(config.orphanExcludeFolders).toEqual(["Daily Notes", "Templates", "Profile"]) + expect(config.orphanExcludeFoldersOverride).toBeNull() }) it.each([ @@ -125,9 +125,9 @@ describe("loadConfig", () => { expect(config.dailyNotesFolder).toBe("Journal") }) - it("cascades into orphanExcludeFolders when ORPHAN_EXCLUDE_FOLDERS is not set", () => { + it("leaves the orphan override unset when ORPHAN_EXCLUDE_FOLDERS is not set", () => { const config = loadConfig({ DAILY_NOTES_FOLDER: "Journal" }) - expect(config.orphanExcludeFolders).toEqual(["Journal", "Templates", "About Me"]) + expect(config.orphanExcludeFoldersOverride).toBeNull() }) it("keeps the explicit PROTECTED_PATHS list when DAILY_NOTES_FOLDER is set", () => { @@ -290,11 +290,19 @@ describe("loadConfig", () => { }) describe("ORPHAN_EXCLUDE_FOLDERS (comma-separated)", () => { + it.each(["", " "])("treats %j as no override", (input) => { + expect(loadConfig({ ORPHAN_EXCLUDE_FOLDERS: input }).orphanExcludeFoldersOverride).toBeNull() + }) + + it("preserves a comma-only explicit empty override", () => { + expect(loadConfig({ ORPHAN_EXCLUDE_FOLDERS: ", ," }).orphanExcludeFoldersOverride).toEqual([]) + }) + it("overrides the default entirely", () => { const config = loadConfig({ ORPHAN_EXCLUDE_FOLDERS: "Archive,Scratch", }) - expect(config.orphanExcludeFolders).toEqual(["Archive", "Scratch"]) + expect(config.orphanExcludeFoldersOverride).toEqual(["Archive", "Scratch"]) }) it("does not include MEMORY_DIR when explicitly set", () => { @@ -302,8 +310,8 @@ describe("loadConfig", () => { MEMORY_DIR: "Profile", ORPHAN_EXCLUDE_FOLDERS: "Archive,Scratch", }) - expect(config.orphanExcludeFolders).toEqual(["Archive", "Scratch"]) - expect(config.orphanExcludeFolders).not.toContain("Profile") + expect(config.orphanExcludeFoldersOverride).toEqual(["Archive", "Scratch"]) + expect(config.orphanExcludeFoldersOverride).not.toContain("Profile") }) it("validates each entry", () => { diff --git a/src/vault-mcp/config.ts b/src/vault-mcp/config.ts index 6884a4d29..817bdea64 100644 --- a/src/vault-mcp/config.ts +++ b/src/vault-mcp/config.ts @@ -122,7 +122,7 @@ export type VaultConfig = Readonly<{ /** PROTECTED_PATHS as the user set it; null when unset, in which case the * protected set (memory dir + daily notes folder) is resolved per call. */ protectedPathsOverride: readonly string[] | null - orphanExcludeFolders: readonly string[] + orphanExcludeFoldersOverride: readonly string[] | null serviceDocumentationUrl: string /** When true, the embedding pipeline is active — notes are chunked, embedded * via a local ONNX model (bge-small-en-v1.5), and stored in sqlite-vec for @@ -184,15 +184,10 @@ export const loadConfig = (env: Record = process.env const protectedPathsRaw = env.PROTECTED_PATHS?.trim() const protectedPathsOverride = protectedPathsRaw ? parseVaultFolderList(protectedPathsRaw) : null - // The orphan default tracks the env-configured daily notes folder only - // (the vault's daily-notes.json can't cascade here — config load is - // synchronous env parsing; the file is read lazily at call time). - const dailyNotesFolderOrDefault = dailyNotesFolder ?? "Daily Notes" - const orphanExcludeFoldersRaw = env.ORPHAN_EXCLUDE_FOLDERS?.trim() - const orphanExcludeFolders = orphanExcludeFoldersRaw + const orphanExcludeFoldersOverride = orphanExcludeFoldersRaw ? parseVaultFolderList(orphanExcludeFoldersRaw) - : [dailyNotesFolderOrDefault, "Templates", memoryDir] + : null const serviceDocumentationUrl = env.SERVICE_DOCUMENTATION_URL?.trim() ? z.string().url().parse(env.SERVICE_DOCUMENTATION_URL.trim()) @@ -299,7 +294,7 @@ export const loadConfig = (env: Record = process.env dailyNotesFolder, dailyNotesFormat, protectedPathsOverride, - orphanExcludeFolders, + orphanExcludeFoldersOverride, serviceDocumentationUrl, embeddingEnabled, rerankMode, diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json index 320f540df..91a0ec8b7 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json @@ -272,12 +272,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json index 6b652140b..c8b996e82 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json @@ -274,12 +274,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json index 388b9aeb4..de2d99f0b 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json @@ -274,12 +274,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json index 1c35b4dc7..feb9a399f 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json @@ -275,12 +275,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json index bde9251c8..2df85c5f6 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json @@ -274,12 +274,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json index 906158bf4..ca7108792 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json @@ -229,12 +229,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json index c4240e468..c4b79ce62 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json @@ -230,12 +230,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json index fa4466c9a..7515c4097 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json @@ -229,12 +229,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json index dcb406f0f..9d0bb2f65 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json @@ -228,12 +228,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json index 5f8c6e662..2e40dd919 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json @@ -274,12 +274,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json index f20ec2869..871fe0365 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json @@ -9,12 +9,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json index 453ed3dc6..bcad025a4 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json @@ -10,12 +10,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json index 2b29b4d2c..8ff0848a7 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json @@ -9,12 +9,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json index 491cc49f7..21a1a987e 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json @@ -10,12 +10,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json index 490b597be..157114586 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json @@ -11,12 +11,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json index ca623c057..66c547acd 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json @@ -10,12 +10,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json index e6f2d4b9d..a61c3c5ae 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json @@ -9,12 +9,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json index 4a2a36232..01d7d56e5 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json @@ -8,12 +8,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({ exclude_folders: [\"Daily Notes\",\"Templates\",\"About Me\"] })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folders to exclude (default [\"Daily Notes\",\"Templates\",\"About Me\"])", + "description": "Folder paths to exclude (e.g. Projects)", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts b/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts index 4ac482e70..88dedfd42 100644 --- a/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts +++ b/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts @@ -4,14 +4,14 @@ import { mkdtemp, rm, writeFile, mkdir, readFile, utimes } from "node:fs/promise import { join } from "node:path" import { tmpdir } from "node:os" import { DateTime } from "luxon" -import type { z } from "zod" +import { z } from "zod" import { computeEnabledToolNames, registerTools } from "../tool-definitions.js" import { TOOL_NAMES, TOOL_REGISTRY } from "../tool-registry.js" import { loadConfig } from "../../config.js" import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" import { createSearchIndex } from "../../search/search-index.js" import type { SearchIndex } from "../../search/search-index.js" -import { logger } from "../../../logger.js" +import { logger, type Logger } from "../../../logger.js" const ALL_TOOL_NAMES = Object.values(TOOL_NAMES) @@ -72,13 +72,16 @@ beforeEach(() => { }) /** The registerTool calls a server makes under the config this env produces. */ -const registerWithConfig = (env: Record): RegisterToolCall[] => { +const registerWithConfig = ( + env: Record, + context: { vaultPath?: string; search?: SearchIndex; logger?: Logger } = {}, +): RegisterToolCall[] => { const server = { registerTool: vi.fn() } registerTools({ server: server as unknown as McpServer, - vaultPath: "/test-vault", - search: {} as SearchIndex, - logger, + vaultPath: context.vaultPath ?? "/test-vault", + search: context.search ?? ({} as SearchIndex), + logger: context.logger ?? logger, config: loadConfig(env), }) return server.registerTool.mock.calls as RegisterToolCall[] @@ -1403,6 +1406,234 @@ describe("vault_search handler", () => { }) }) +describe("vault_find_orphans live folder defaults", () => { + const setupOrphans = async ( + options: { + settings?: string + env?: Record + paths?: readonly string[] + } = {}, + ) => { + const vaultPath = await mkdtemp(join(tmpdir(), "orphan-handler-")) + onTestFinished(() => rm(vaultPath, { recursive: true, force: true })) + await mkdir(join(vaultPath, ".obsidian")) + const settingsPath = join(vaultPath, ".obsidian/daily-notes.json") + + if (options.settings !== undefined) await writeFile(settingsPath, options.settings) + + const search = createSearchIndex(":memory:") + const paths = options.paths ?? [ + "Journal/daily.md", + "Journal/nested/daily.md", + "JournalOld/note.md", + "ordinary.md", + "Daily Notes/daily.md", + "Templates/template.md", + "About Me/memory.md", + "Archive/note.md", + ] + paths.forEach((filePath, index) => { + search.upsertNote( + { filePath, rawContent: "# Note\n", fileStat: { mtimeMs: 10000 - index, size: 7 } }, + logger, + ) + }) + const requestLogger: Logger = { ...logger, warn: vi.fn(), child: () => requestLogger } + const registeredCalls = registerWithConfig(options.env ?? {}, { + vaultPath, + search, + logger: requestLogger, + }) + const orphanCall = registeredCalls.find(([name]) => name === TOOL_NAMES.VAULT_FIND_ORPHANS) + + if (!orphanCall) throw new Error("vault_find_orphans not registered") + + const queryPaths = async (args: { exclude_folders?: string[]; limit?: number } = {}) => { + const result = z + .object({ + content: z.array(z.object({ text: z.string() })), + isError: z.boolean().optional(), + }) + .parse(await orphanCall[2](args, { requestId: "orphan-request" })) + expect(result.isError).toBeUndefined() + return z + .array(z.object({ path: z.string() })) + .parse(JSON.parse(requireTextContent(result))) + .map((note) => note.path) + } + return { settingsPath, queryPaths, requestLogger, toolConfig: orphanCall[1] } + } + + it("excludes a file-only daily folder and descendants but keeps sibling decoys", async () => { + const { queryPaths } = await setupOrphans({ settings: '{"folder":"Journal"}' }) + expect(await queryPaths()).toEqual([ + "JournalOld/note.md", + "ordinary.md", + "Daily Notes/daily.md", + "Archive/note.md", + ]) + }) + + it("excludes daily notes before a limit of one", async () => { + const { queryPaths } = await setupOrphans({ settings: '{"folder":"Journal"}' }) + expect(await queryPaths({ limit: 1 })).toEqual(["JournalOld/note.md"]) + }) + + it("uses the env daily folder over the file folder without reading malformed settings", async () => { + const { queryPaths, requestLogger } = await setupOrphans({ + settings: "broken", + env: { DAILY_NOTES_FOLDER: "Journal" }, + }) + expect(await queryPaths()).toEqual([ + "JournalOld/note.md", + "ordinary.md", + "Daily Notes/daily.md", + "Archive/note.md", + ]) + expect(requestLogger.warn).not.toHaveBeenCalled() + }) + + it("replaces all defaults with the explicit environment list", async () => { + const { queryPaths, requestLogger } = await setupOrphans({ + settings: "broken", + env: { ORPHAN_EXCLUDE_FOLDERS: "Archive" }, + }) + expect(await queryPaths()).toEqual([ + "Journal/daily.md", + "Journal/nested/daily.md", + "JournalOld/note.md", + "ordinary.md", + "Daily Notes/daily.md", + "Templates/template.md", + "About Me/memory.md", + ]) + expect(requestLogger.warn).not.toHaveBeenCalled() + }) + + it("lets a request list replace the environment list", async () => { + const { queryPaths } = await setupOrphans({ env: { ORPHAN_EXCLUDE_FOLDERS: "Archive" } }) + expect(await queryPaths({ exclude_folders: ["Journal"] })).toEqual([ + "JournalOld/note.md", + "ordinary.md", + "Daily Notes/daily.md", + "Templates/template.md", + "About Me/memory.md", + "Archive/note.md", + ]) + }) + + it("returns every folder for request [] and bypasses malformed settings", async () => { + const { queryPaths, requestLogger } = await setupOrphans({ + settings: "broken", + env: { ORPHAN_EXCLUDE_FOLDERS: "Archive" }, + }) + expect(await queryPaths({ exclude_folders: [] })).toEqual([ + "Journal/daily.md", + "Journal/nested/daily.md", + "JournalOld/note.md", + "ordinary.md", + "Daily Notes/daily.md", + "Templates/template.md", + "About Me/memory.md", + "Archive/note.md", + ]) + expect(requestLogger.warn).not.toHaveBeenCalled() + }) + + it("uses a comma-only environment list as no exclusions", async () => { + const { queryPaths, requestLogger } = await setupOrphans({ + settings: "broken", + env: { ORPHAN_EXCLUDE_FOLDERS: ", ," }, + }) + expect(await queryPaths()).toEqual([ + "Journal/daily.md", + "Journal/nested/daily.md", + "JournalOld/note.md", + "ordinary.md", + "Daily Notes/daily.md", + "Templates/template.md", + "About Me/memory.md", + "Archive/note.md", + ]) + expect(requestLogger.warn).not.toHaveBeenCalled() + }) + + it("excludes a custom memory directory even when memory is disabled", async () => { + const { queryPaths } = await setupOrphans({ + env: { MEMORY_DIR: "Profile", MEMORY_ENABLED: "false" }, + paths: ["Profile/memory.md", "Profile/sub/memory.md", "About Me/note.md", "ordinary.md"], + }) + expect(await queryPaths()).toEqual(["About Me/note.md", "ordinary.md"]) + }) + + it("applies file folder changes without registering the handler again", async () => { + const { queryPaths, settingsPath } = await setupOrphans({ + settings: '{"folder":"Journal"}', + paths: ["Journal/daily.md", "Planner/Daily/daily.md", "ordinary.md"], + }) + expect(await queryPaths()).toEqual(["Planner/Daily/daily.md", "ordinary.md"]) + await writeFile(settingsPath, '{"folder":"Planner/Daily"}') + expect(await queryPaths()).toEqual(["Journal/daily.md", "ordinary.md"]) + }) + + it("follows valid, malformed, and repaired settings in the same process", async () => { + const { queryPaths, settingsPath, requestLogger } = await setupOrphans({ + settings: '{"folder":"Journal"}', + paths: ["Journal/daily.md", "Daily Notes/daily.md", "Planner/Daily/daily.md", "ordinary.md"], + }) + expect(await queryPaths()).toEqual([ + "Daily Notes/daily.md", + "Planner/Daily/daily.md", + "ordinary.md", + ]) + await writeFile(settingsPath, "broken") + expect(await queryPaths()).toEqual([ + "Journal/daily.md", + "Planner/Daily/daily.md", + "ordinary.md", + ]) + expect(requestLogger.warn).toHaveBeenCalledTimes(1) + expect(requestLogger.warn).toHaveBeenCalledWith( + "cannot read daily notes config, using defaults", + { error: expect.any(String) }, + ) + await writeFile(settingsPath, '{"folder":"Planner/Daily"}') + expect(await queryPaths()).toEqual(["Journal/daily.md", "Daily Notes/daily.md", "ordinary.md"]) + }) + + it("uses settings that arrive after handler registration", async () => { + const { queryPaths, settingsPath } = await setupOrphans({ + paths: ["Journal/daily.md", "Daily Notes/daily.md", "ordinary.md"], + }) + expect(await queryPaths()).toEqual(["Journal/daily.md", "ordinary.md"]) + await writeFile(settingsPath, '{"folder":"Journal"}') + expect(await queryPaths()).toEqual(["Daily Notes/daily.md", "ordinary.md"]) + }) + + it("describes live default sources and keeps the schema to parameter meaning", async () => { + const { toolConfig } = await setupOrphans({ settings: '{"folder":"Journal"}' }) + expect(toolConfig.description).toContain( + 'the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → "Daily Notes"), "Templates", and "About Me"', + ) + expect(toolConfig.description).not.toContain("Journal") + expect(toolConfig.inputSchema?.exclude_folders?.description).toBe( + "Folder paths to exclude (e.g. Projects)", + ) + }) + + it("describes an explicit environment list instead of implicit default sources", async () => { + const { toolConfig } = await setupOrphans({ + env: { ORPHAN_EXCLUDE_FOLDERS: "Archive,Scratch" }, + }) + const defaultsLine = toolConfig.description + ?.split("\n") + .find((line) => line.startsWith("- With exclude_folders")) + expect(defaultsLine).toBe( + '- With exclude_folders omitted, the ORPHAN_EXCLUDE_FOLDERS override excludes ["Archive","Scratch"].', + ) + }) +}) + describe("vault_list_tasks handler", () => { const mockExtra = { requestId: "test-1", sessionId: "session-1" } diff --git a/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts b/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts index 7c5de7620..436f5cd85 100644 --- a/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts +++ b/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts @@ -3,6 +3,7 @@ import { mkdtemp, rm, writeFile, mkdir } from "node:fs/promises" import { join } from "node:path" import { tmpdir } from "node:os" import { registerPrompts } from "../../prompt-definitions.js" +import { readDailyNotesConfig } from "../../../vault-operations/daily-notes.js" import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" import { type RegisterPromptCall, @@ -20,12 +21,200 @@ import { logger, } from "./prompt-test-harness.js" +vi.mock("../../../vault-operations/daily-notes.js", { spy: true }) + afterEach(() => { vi.restoreAllMocks() }) // ── vault-orientation handler ──────────────────────────────────── +describe("vault-orientation live orphan folders", () => { + const setupOrphanPrompt = async ( + options: { + settings?: string + env?: Record + paths?: readonly string[] + } = {}, + ) => { + const vault = await mkdtemp(join(tmpdir(), "orientation-orphans-")) + onTestFinished(() => rm(vault, { recursive: true, force: true })) + await mkdir(join(vault, ".obsidian")) + const settingsPath = join(vault, ".obsidian/daily-notes.json") + + if (options.settings !== undefined) await writeFile(settingsPath, options.settings) + + const search = createSearchIndex(":memory:") + const paths = options.paths ?? [ + "Journal/daily.md", + "Journal/nested/daily.md", + "JournalOld/note.md", + "ordinary.md", + "Daily Notes/daily.md", + "Templates/template.md", + "Profile/memory.md", + "Archive/note.md", + ] + paths.forEach((filePath, index) => { + search.upsertNote( + { filePath, rawContent: "", fileStat: { mtimeMs: 10000 - index, size: 0 } }, + logger, + ) + }) + const logCalls: LogCall[] = [] + const config = loadConfig({ MEMORY_ENABLED: "false", MEMORY_DIR: "Profile", ...options.env }) + const calls = registerWithSearch(vault, search, recordingLogger(logCalls), config) + const handler = findCall(calls, PROMPT_NAMES.VAULT_ORIENTATION)[2] + const orphanSection = async () => { + const text = textOf(await handler(fakeExtra)) + const section = text.split("## Orphans\n")[1]?.split("\n\n---")[0]?.split("\n\n## ")[0] + + if (!section) throw new Error("prompt has no orphan section") + return section + } + return { settingsPath, orphanSection, logCalls, config } + } + + it("excludes file-configured daily notes and descendants while retaining sibling folders", async () => { + const { orphanSection } = await setupOrphanPrompt({ settings: '{"folder":"Journal"}' }) + expect(await orphanSection()).toBe( + "4 orphan notes (no incoming links):\n- JournalOld/note.md — note\n- ordinary.md — ordinary\n- Daily Notes/daily.md — daily\n- Archive/note.md — note", + ) + }) + + it("keeps ordinary orphans visible behind more than five newer excluded daily candidates", async () => { + const dailyPaths = Array.from({ length: 7 }, (_, index) => `Journal/nested/day-${index}.md`) + const { orphanSection } = await setupOrphanPrompt({ + settings: '{"folder":"Journal"}', + paths: [...dailyPaths, "ordinary.md", "JournalOld/note.md"], + }) + expect(await orphanSection()).toBe( + "2 orphan notes (no incoming links):\n- ordinary.md — ordinary\n- JournalOld/note.md — note", + ) + }) + + it("lets explicit environment folders replace all defaults", async () => { + const { orphanSection } = await setupOrphanPrompt({ + settings: '{"folder":"Journal"}', + env: { ORPHAN_EXCLUDE_FOLDERS: "Archive" }, + paths: [ + "Journal/daily.md", + "Daily Notes/daily.md", + "Templates/template.md", + "Profile/memory.md", + "Archive/note.md", + ], + }) + expect(await orphanSection()).toBe( + "4 orphan notes (no incoming links):\n- Journal/daily.md — daily\n- Daily Notes/daily.md — daily\n- Templates/template.md — template\n- Profile/memory.md — memory", + ) + }) + + it("uses the environment daily folder over the file folder", async () => { + const { orphanSection } = await setupOrphanPrompt({ + settings: '{"folder":"Journal"}', + env: { DAILY_NOTES_FOLDER: "Env/Daily" }, + paths: [ + "Env/Daily/daily.md", + "Env/Daily/nested/daily.md", + "Journal/daily.md", + "ordinary.md", + "Templates/template.md", + "Profile/memory.md", + ], + }) + expect(await orphanSection()).toBe( + "2 orphan notes (no incoming links):\n- Journal/daily.md — daily\n- ordinary.md — ordinary", + ) + }) + + it("honors an empty environment list and still reads daily settings once for broken links", async () => { + const { orphanSection, config } = await setupOrphanPrompt({ + settings: '{"folder":"Journal"}', + env: { ORPHAN_EXCLUDE_FOLDERS: ", ," }, + paths: [ + "Journal/daily.md", + "Daily Notes/daily.md", + "Templates/template.md", + "Profile/memory.md", + ], + }) + vi.mocked(readDailyNotesConfig).mockClear() + expect(await orphanSection()).toBe( + "4 orphan notes (no incoming links):\n- Journal/daily.md — daily\n- Daily Notes/daily.md — daily\n- Templates/template.md — template\n- Profile/memory.md — memory", + ) + expect(vi.mocked(readDailyNotesConfig)).toHaveBeenCalledTimes(1) + expect(vi.mocked(readDailyNotesConfig)).toHaveBeenCalledWith( + { + vaultPath: expect.any(String), + envSettings: { folder: config.dailyNotesFolder, format: config.dailyNotesFormat }, + }, + expect.any(Object), + ) + }) + + it("uses the same single daily settings read for default exclusions and broken links", async () => { + const { orphanSection } = await setupOrphanPrompt({ settings: '{"folder":"Journal"}' }) + vi.mocked(readDailyNotesConfig).mockClear() + expect(await orphanSection()).toBe( + "4 orphan notes (no incoming links):\n- JournalOld/note.md — note\n- ordinary.md — ordinary\n- Daily Notes/daily.md — daily\n- Archive/note.md — note", + ) + expect(vi.mocked(readDailyNotesConfig)).toHaveBeenCalledTimes(1) + }) + + it("changes folder exclusions on the next invocation without registering again", async () => { + const { orphanSection, settingsPath } = await setupOrphanPrompt({ + settings: '{"folder":"Journal"}', + paths: ["Journal/daily.md", "Planner/Daily/daily.md", "ordinary.md"], + }) + expect(await orphanSection()).toBe( + "2 orphan notes (no incoming links):\n- Planner/Daily/daily.md — daily\n- ordinary.md — ordinary", + ) + await writeFile(settingsPath, '{"folder":"Planner/Daily"}') + expect(await orphanSection()).toBe( + "2 orphan notes (no incoming links):\n- Journal/daily.md — daily\n- ordinary.md — ordinary", + ) + }) + + it("falls back with a warning for malformed settings and uses a repaired file on the next call", async () => { + const { orphanSection, settingsPath, logCalls } = await setupOrphanPrompt({ + settings: '{"folder":"Journal"}', + paths: ["Journal/daily.md", "Daily Notes/daily.md", "Planner/Daily/daily.md", "ordinary.md"], + }) + expect(await orphanSection()).toBe( + "3 orphan notes (no incoming links):\n- Daily Notes/daily.md — daily\n- Planner/Daily/daily.md — daily\n- ordinary.md — ordinary", + ) + await writeFile(settingsPath, "broken") + expect(await orphanSection()).toBe( + "3 orphan notes (no incoming links):\n- Journal/daily.md — daily\n- Planner/Daily/daily.md — daily\n- ordinary.md — ordinary", + ) + expect(logCalls.filter((entry) => entry.level === "warn")).toEqual([ + { + level: "warn", + message: "cannot read daily notes config, using defaults", + data: { requestId: "1", prompt: "vault-orientation", error: expect.any(String) }, + }, + ]) + await writeFile(settingsPath, '{"folder":"Planner/Daily"}') + expect(await orphanSection()).toBe( + "3 orphan notes (no incoming links):\n- Journal/daily.md — daily\n- Daily Notes/daily.md — daily\n- ordinary.md — ordinary", + ) + }) + + it("uses file settings that arrive after prompt registration", async () => { + const { orphanSection, settingsPath } = await setupOrphanPrompt({ + paths: ["Journal/daily.md", "Daily Notes/daily.md", "ordinary.md"], + }) + expect(await orphanSection()).toBe( + "2 orphan notes (no incoming links):\n- Journal/daily.md — daily\n- ordinary.md — ordinary", + ) + await writeFile(settingsPath, '{"folder":"Journal"}') + expect(await orphanSection()).toBe( + "2 orphan notes (no incoming links):\n- Daily Notes/daily.md — daily\n- ordinary.md — ordinary", + ) + }) +}) + describe("vault-orientation handler", () => { it("returns sentinels and never throws on an empty vault", async () => { const config = loadConfig({}) diff --git a/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts b/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts index 4b1986094..a636e45c5 100644 --- a/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts +++ b/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts @@ -2,6 +2,7 @@ import { createMemoryStore, type MemoryFileOutline } from "../../vault-operations/memory-store.js" import { vaultFs } from "../../vault-operations/vault-filesystem.js" +import { resolveEffectiveOrphanExcludeFolders } from "../../vault-operations/vault-folder-config.js" import { readDailyNotesConfig } from "../../vault-operations/daily-notes.js" import { describeError } from "../../../utils/describe-error.js" import { compareByUtf8Bytes } from "../../../utils/compare-utf8-bytes.js" @@ -161,22 +162,28 @@ export const registerVaultOrientationPrompt = ({ config.memoryEnabled && memoryStore ? await memoryStore.listMemoryFiles({ vaultPath }, reqLogger) : [] - const orphanResults = search.findOrphans( + const dailyNotesConfig = await readDailyNotesConfig( { - excludeFolders: [...config.orphanExcludeFolders], - limit: ORIENTATION_ORPHAN_LIMIT + 1, + vaultPath, + envSettings: { folder: config.dailyNotesFolder, format: config.dailyNotesFormat }, }, reqLogger, ) - const hasMoreOrphans = orphanResults.length > ORIENTATION_ORPHAN_LIMIT - const orphans = orphanResults.slice(0, ORIENTATION_ORPHAN_LIMIT) - const dailyNotesConfig = await readDailyNotesConfig( + const orphanResults = search.findOrphans( { - vaultPath, - envSettings: { folder: config.dailyNotesFolder, format: config.dailyNotesFormat }, + excludeFolders: [ + ...resolveEffectiveOrphanExcludeFolders({ + orphanExcludeFoldersOverride: config.orphanExcludeFoldersOverride, + memoryDir: config.memoryDir, + dailyNotesFolder: dailyNotesConfig.folder, + }), + ], + limit: ORIENTATION_ORPHAN_LIMIT + 1, }, reqLogger, ) + const hasMoreOrphans = orphanResults.length > ORIENTATION_ORPHAN_LIMIT + const orphans = orphanResults.slice(0, ORIENTATION_ORPHAN_LIMIT) const brokenLinkResult = search.brokenLinkCount( { dailyNotesFolder: dailyNotesConfig.folder }, reqLogger, diff --git a/src/vault-mcp/mcp-core/tools/__tests__/vault-crud-tools.test.ts b/src/vault-mcp/mcp-core/tools/__tests__/vault-crud-tools.test.ts deleted file mode 100644 index edba05d50..000000000 --- a/src/vault-mcp/mcp-core/tools/__tests__/vault-crud-tools.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest" -import type { VaultConfig } from "../../../config.js" -import { logger } from "../../../../logger.js" - -vi.mock("../../../vault-operations/daily-notes.js", () => ({ - readDailyNotesFileConfig: vi.fn(), -})) - -import { resolveEffectiveProtectedPaths } from "../vault-crud-tools.js" -import { readDailyNotesFileConfig } from "../../../vault-operations/daily-notes.js" - -const mockedReadDailyNotesFileConfig = vi.mocked(readDailyNotesFileConfig) - -const makeConfig = ( - overrides: Partial< - Pick< - VaultConfig, - "memoryDir" | "protectedPathsOverride" | "dailyNotesFolder" | "dailyNotesFormat" - > - > = {}, -): VaultConfig => - ({ - memoryDir: overrides.memoryDir ?? "About Me", - protectedPathsOverride: overrides.protectedPathsOverride ?? null, - dailyNotesFolder: overrides.dailyNotesFolder, - dailyNotesFormat: overrides.dailyNotesFormat, - }) as unknown as VaultConfig - -describe("resolveEffectiveProtectedPaths", () => { - beforeEach(() => { - mockedReadDailyNotesFileConfig.mockReset() - }) - - it("returns the user's list unchanged when PROTECTED_PATHS is set", async () => { - const config = makeConfig({ protectedPathsOverride: ["Secrets", "Custom"] }) - const result = await resolveEffectiveProtectedPaths({ config, vaultPath: "/vault" }, logger) - - expect(result).toEqual(["Secrets", "Custom"]) - expect(mockedReadDailyNotesFileConfig).not.toHaveBeenCalled() - }) - - it("protects the memory dir plus the file-configured daily folder by default", async () => { - mockedReadDailyNotesFileConfig.mockResolvedValue({ - folder: "Journal", - format: "YYYY-MM-DD", - }) - const requestLogger = logger.child({ requestId: "request-1" }) - const config = makeConfig() - const result = await resolveEffectiveProtectedPaths( - { config, vaultPath: "/vault" }, - requestLogger, - ) - - expect(result).toEqual(["About Me", "Journal"]) - expect(mockedReadDailyNotesFileConfig).toHaveBeenCalledTimes(1) - expect(mockedReadDailyNotesFileConfig).toHaveBeenCalledWith("/vault", requestLogger) - }) - - it("uses the configured memory dir in the default set", async () => { - mockedReadDailyNotesFileConfig.mockResolvedValue({ - folder: "Daily Notes", - format: "YYYY-MM-DD", - }) - const config = makeConfig({ memoryDir: "Profile" }) - const result = await resolveEffectiveProtectedPaths({ config, vaultPath: "/vault" }, logger) - - expect(result).toEqual(["Profile", "Daily Notes"]) - }) - - it("protects only the memory dir when the resolved daily folder is blank", async () => { - mockedReadDailyNotesFileConfig.mockResolvedValue({ - folder: " ", - format: "YYYY-MM-DD", - }) - const config = makeConfig() - const result = await resolveEffectiveProtectedPaths({ config, vaultPath: "/vault" }, logger) - - expect(result).toEqual(["About Me"]) - }) - - it("protects DAILY_NOTES_FOLDER without reading the file, even when the format is unset", async () => { - const config = makeConfig({ dailyNotesFolder: "Journal" }) - const result = await resolveEffectiveProtectedPaths({ config, vaultPath: "/vault" }, logger) - - expect(result).toEqual(["About Me", "Journal"]) - expect(mockedReadDailyNotesFileConfig).not.toHaveBeenCalled() - }) - - it("reads the file for the folder when only DAILY_NOTES_FORMAT is set", async () => { - mockedReadDailyNotesFileConfig.mockResolvedValue({ - folder: "Journal", - format: "YYYY-MM-DD", - }) - const config = makeConfig({ dailyNotesFormat: "DD-MM-YYYY" }) - const result = await resolveEffectiveProtectedPaths({ config, vaultPath: "/vault" }, logger) - - expect(result).toEqual(["About Me", "Journal"]) - expect(mockedReadDailyNotesFileConfig).toHaveBeenCalledTimes(1) - }) - - it("rejects when the file exists but cannot be read, instead of protecting the default folder", async () => { - mockedReadDailyNotesFileConfig.mockRejectedValue( - new Error("cannot read daily notes config from .obsidian/daily-notes.json"), - ) - const config = makeConfig() - - await expect( - resolveEffectiveProtectedPaths({ config, vaultPath: "/vault" }, logger), - ).rejects.toThrow(new Error("cannot read daily notes config from .obsidian/daily-notes.json")) - }) -}) diff --git a/src/vault-mcp/mcp-core/tools/search-tools.ts b/src/vault-mcp/mcp-core/tools/search-tools.ts index 695f2fb1a..8914cd79f 100644 --- a/src/vault-mcp/mcp-core/tools/search-tools.ts +++ b/src/vault-mcp/mcp-core/tools/search-tools.ts @@ -3,6 +3,7 @@ import { z } from "zod" import { TOOL_NAMES } from "../tool-registry.js" import type { ToolRegistrationContext } from "./tool-helpers.js" +import { readEffectiveOrphanExcludeFolders } from "../../vault-operations/vault-folder-config.js" import { readDailyNotesConfig } from "../../vault-operations/daily-notes.js" import { safeHandler, formatNoteMetadata, dateFilterSchema } from "./tool-helpers.js" @@ -610,20 +611,26 @@ Errors: }, ) + const orphanDefaultDescription = config.orphanExcludeFoldersOverride + ? `With exclude_folders omitted, the ORPHAN_EXCLUDE_FOLDERS override excludes ${JSON.stringify(config.orphanExcludeFoldersOverride)}.` + : `With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → "Daily Notes"), "Templates", and ${JSON.stringify(config.memoryDir)}, resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.` + registerTool( TOOL_NAMES.VAULT_FIND_ORPHANS, { title: "Find Orphans", description: `Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored). -Example: vault_find_orphans({ exclude_folders: ${JSON.stringify(config.orphanExcludeFolders)} }) +Example: vault_find_orphans({}) +Example: vault_find_orphans({ exclude_folders: ["Archive"], limit: 10 }) When to use: Vault maintenance — surfacing notes to integrate into the graph.${whenToolEnabledText("vault_patch_note", " Link an orphan by mentioning it from a relevant note with vault_patch_note.")} Prefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault. Parameters: -- exclude_folders replaces the defaults, it does not add to them — include the defaults yourself to keep them. Each entry names a whole folder, subfolders included ("Projects" also excludes "Projects/Archive" but not "ProjectsOld/"), ignoring ASCII letter case. -- limit (default 50) applies after sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check. +- ${orphanDefaultDescription} +- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included ("Projects" also excludes "Projects/Archive" but not "ProjectsOld/"), ignoring ASCII letter case. +- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check. Errors: - An empty array means no orphans were found (after exclusions), not an error. @@ -633,7 +640,7 @@ Returns: JSON array of note metadata (path, title, tags, related, folder, type, exclude_folders: z .array(z.string().min(1)) .optional() - .describe(`Folders to exclude (default ${JSON.stringify(config.orphanExcludeFolders)})`), + .describe("Folder paths to exclude (e.g. Projects)"), limit: z.number().int().min(1).optional().default(50).describe("Max results (default 50)"), }, }, @@ -646,9 +653,12 @@ Returns: JSON array of note metadata (path, title, tags, related, folder, type, return safeHandler( reqLogger, async () => { + const excludeFolders = + exclude_folders ?? + (await readEffectiveOrphanExcludeFolders({ config, vaultPath }, reqLogger)) return search.findOrphans( { - excludeFolders: exclude_folders ?? [...config.orphanExcludeFolders], + excludeFolders: [...excludeFolders], limit, }, reqLogger, diff --git a/src/vault-mcp/mcp-core/tools/vault-crud-tools.ts b/src/vault-mcp/mcp-core/tools/vault-crud-tools.ts index afdaf0a1a..8b1747280 100644 --- a/src/vault-mcp/mcp-core/tools/vault-crud-tools.ts +++ b/src/vault-mcp/mcp-core/tools/vault-crud-tools.ts @@ -2,10 +2,9 @@ import { z } from "zod" import type { VaultConfig } from "../../config.js" -import type { Logger } from "../../../logger.js" import { vaultFs, resolveVaultRelativePath } from "../../vault-operations/vault-filesystem.js" import { noteMover } from "../../vault-operations/note-mover.js" -import { readDailyNotesFileConfig } from "../../vault-operations/daily-notes.js" +import { resolveEffectiveProtectedPaths } from "../../vault-operations/vault-folder-config.js" import { readTrashConfig } from "../../vault-operations/trash-config.js" import { vaultPatcher } from "../../vault-operations/vault-patcher.js" import type { DisplacedLeadingContent } from "../../vault-operations/vault-patcher.js" @@ -30,32 +29,6 @@ const describeDisplacedLeadingContent = ({ return `The ${bytes} bytes of pre-existing content above the note's first heading are now nested under the inserted heading. To add a section above the first heading without pulling existing content into it, use operation "insert_before" with heading "${firstHeading.text}" (H${firstHeading.level}).` } -/** The folders that delete and move refuse to touch. - * - PROTECTED_PATHS, when set, replaces the defaults entirely, so only the - * folders it lists are protected. - * - Otherwise the memory dir (protected even when MEMORY_ENABLED is false) - * plus the daily notes folder, resolved on each call (DAILY_NOTES_FOLDER → - * .obsidian/daily-notes.json → "Daily Notes") so a folder configured only - * in the vault is protected too. - * Throws when daily-notes.json exists but cannot be read, since the folder - * it names is then unknown; DAILY_NOTES_FOLDER or PROTECTED_PATHS bypasses - * the file. */ -export const resolveEffectiveProtectedPaths = async ( - { config, vaultPath }: { config: VaultConfig; vaultPath: string }, - logger: Logger, -): Promise => { - if (config.protectedPathsOverride) return config.protectedPathsOverride - - // loadConfig trims DAILY_NOTES_FOLDER, so a set value is never blank. - if (config.dailyNotesFolder) return [config.memoryDir, config.dailyNotesFolder] - - const dailyNotesConfig = await readDailyNotesFileConfig(vaultPath, logger) - - // A whitespace-only folder in daily-notes.json protects nothing. - const dailyFolder = dailyNotesConfig.folder.trim() - return dailyFolder ? [config.memoryDir, dailyFolder] : [config.memoryDir] -} - /** Protected-path list for tool descriptions. Descriptions are built once at * registration and the daily notes folder is resolved per call, so the text * names that folder's sources, not its value ("Daily Notes" restates the diff --git a/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts b/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts new file mode 100644 index 000000000..0f180538f --- /dev/null +++ b/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts @@ -0,0 +1,202 @@ +import { describe, expect, it, onTestFinished, vi } from "vitest" +import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { loadConfig } from "../../config.js" +import { logger } from "../../../logger.js" +import { + readEffectiveOrphanExcludeFolders, + resolveEffectiveOrphanExcludeFolders, + resolveEffectiveProtectedPaths, +} from "../vault-folder-config.js" + +const createVault = async (settings?: string): Promise => { + const vaultPath = await mkdtemp(join(tmpdir(), "vault-folder-config-")) + onTestFinished(() => rm(vaultPath, { recursive: true, force: true })) + await mkdir(join(vaultPath, ".obsidian")) + + if (settings !== undefined) { + await writeFile(join(vaultPath, ".obsidian/daily-notes.json"), settings) + } + return vaultPath +} + +describe("resolveEffectiveProtectedPaths", () => { + it("returns the user's list unchanged without reading malformed daily settings", async () => { + const vaultPath = await createVault("broken") + const requestLogger = { ...logger, warn: vi.fn() } + const config = loadConfig({ PROTECTED_PATHS: "Secrets,Custom" }) + + expect(await resolveEffectiveProtectedPaths({ config, vaultPath }, requestLogger)).toEqual([ + "Secrets", + "Custom", + ]) + expect(requestLogger.warn).not.toHaveBeenCalled() + }) + + it("protects the memory dir plus the file-configured daily folder", async () => { + const vaultPath = await createVault('{"folder":"Journal"}') + expect( + await resolveEffectiveProtectedPaths({ config: loadConfig({}), vaultPath }, logger), + ).toEqual(["About Me", "Journal"]) + }) + + it("protects custom memory even when the memory feature is disabled", async () => { + const vaultPath = await createVault() + const config = loadConfig({ MEMORY_DIR: "Profile", MEMORY_ENABLED: "false" }) + expect(await resolveEffectiveProtectedPaths({ config, vaultPath }, logger)).toEqual([ + "Profile", + "Daily Notes", + ]) + }) + + it("protects only memory when the file folder is whitespace-only", async () => { + const vaultPath = await createVault('{"folder":" "}') + expect( + await resolveEffectiveProtectedPaths({ config: loadConfig({}), vaultPath }, logger), + ).toEqual(["About Me"]) + }) + + it("uses DAILY_NOTES_FOLDER without reading the file when the format is unset", async () => { + const vaultPath = await createVault("broken") + const requestLogger = { ...logger, warn: vi.fn() } + const config = loadConfig({ DAILY_NOTES_FOLDER: "Journal" }) + expect(await resolveEffectiveProtectedPaths({ config, vaultPath }, requestLogger)).toEqual([ + "About Me", + "Journal", + ]) + expect(requestLogger.warn).not.toHaveBeenCalled() + }) + + it("reads the file folder when only DAILY_NOTES_FORMAT is set", async () => { + const vaultPath = await createVault('{"folder":"Journal"}') + const config = loadConfig({ DAILY_NOTES_FORMAT: "DD-MM-YYYY" }) + expect(await resolveEffectiveProtectedPaths({ config, vaultPath }, logger)).toEqual([ + "About Me", + "Journal", + ]) + }) + + it("rejects malformed existing settings and propagates the caller's logger", async () => { + const vaultPath = await createVault("broken") + const requestLogger = { ...logger.child({ requestId: "folder-request" }), warn: vi.fn() } + await expect( + resolveEffectiveProtectedPaths({ config: loadConfig({}), vaultPath }, requestLogger), + ).rejects.toThrow(new Error("cannot read daily notes config from .obsidian/daily-notes.json")) + expect(requestLogger.warn).toHaveBeenCalledTimes(1) + expect(requestLogger.warn).toHaveBeenCalledWith("cannot read daily notes config", { + error: expect.any(String), + }) + }) +}) + +describe("resolveEffectiveOrphanExcludeFolders", () => { + it("keeps nonblank file spelling and default folder order", () => { + expect( + resolveEffectiveOrphanExcludeFolders({ + orphanExcludeFoldersOverride: null, + memoryDir: "Profile", + dailyNotesFolder: " Journal/Daily/ ", + }), + ).toEqual([" Journal/Daily/ ", "Templates", "Profile"]) + }) + + it("omits a whitespace-only daily folder", () => { + expect( + resolveEffectiveOrphanExcludeFolders({ + orphanExcludeFoldersOverride: null, + memoryDir: "About Me", + dailyNotesFolder: " ", + }), + ).toEqual(["Templates", "About Me"]) + }) + + it("preserves an explicit empty list", () => { + expect( + resolveEffectiveOrphanExcludeFolders({ + orphanExcludeFoldersOverride: [], + memoryDir: "About Me", + dailyNotesFolder: "Journal", + }), + ).toEqual([]) + }) +}) + +describe("readEffectiveOrphanExcludeFolders", () => { + it.each([ + { + label: "missing settings", + settings: undefined, + env: {}, + expected: ["Daily Notes", "Templates", "About Me"], + }, + { + label: "file-only nested folder", + settings: '{"folder":"Planner/Daily"}', + env: {}, + expected: ["Planner/Daily", "Templates", "About Me"], + }, + { + label: "custom memory with memory disabled", + settings: undefined, + env: { MEMORY_DIR: "Profile", MEMORY_ENABLED: "false" }, + expected: ["Daily Notes", "Templates", "Profile"], + }, + { + label: "format-only env", + settings: '{"folder":"Journal"}', + env: { DAILY_NOTES_FORMAT: "DD-MM-YYYY" }, + expected: ["Journal", "Templates", "About Me"], + }, + { + label: "blank file folder", + settings: '{"folder":" "}', + env: {}, + expected: ["Templates", "About Me"], + }, + ])("uses $label", async ({ settings, env, expected }) => { + const vaultPath = await createVault(settings) + expect( + await readEffectiveOrphanExcludeFolders({ config: loadConfig(env), vaultPath }, logger), + ).toEqual(expected) + }) + + it.each([ + { + label: "daily folder override", + env: { DAILY_NOTES_FOLDER: "Env/Journal" }, + expected: ["Env/Journal", "Templates", "About Me"], + }, + { + label: "explicit orphan override", + env: { ORPHAN_EXCLUDE_FOLDERS: "Archive,Scratch" }, + expected: ["Archive", "Scratch"], + }, + { label: "comma-only empty override", env: { ORPHAN_EXCLUDE_FOLDERS: ", ," }, expected: [] }, + ])("bypasses malformed settings for $label", async ({ env, expected }) => { + const vaultPath = await createVault("broken") + const requestLogger = { ...logger, warn: vi.fn() } + expect( + await readEffectiveOrphanExcludeFolders( + { config: loadConfig(env), vaultPath }, + requestLogger, + ), + ).toEqual(expected) + expect(requestLogger.warn).not.toHaveBeenCalled() + }) + + it("warns and falls back when existing settings are malformed", async () => { + const vaultPath = await createVault("broken") + const requestLogger = { ...logger, warn: vi.fn() } + expect( + await readEffectiveOrphanExcludeFolders({ config: loadConfig({}), vaultPath }, requestLogger), + ).toEqual(["Daily Notes", "Templates", "About Me"]) + expect(requestLogger.warn).toHaveBeenCalledTimes(1) + expect(requestLogger.warn).toHaveBeenCalledWith( + "cannot read daily notes config, using defaults", + { + error: expect.any(String), + }, + ) + }) +}) diff --git a/src/vault-mcp/vault-operations/vault-folder-config.ts b/src/vault-mcp/vault-operations/vault-folder-config.ts new file mode 100644 index 000000000..3aaef9b93 --- /dev/null +++ b/src/vault-mcp/vault-operations/vault-folder-config.ts @@ -0,0 +1,71 @@ +import type { VaultConfig } from "../config.js" +import type { Logger } from "../../logger.js" +import { readDailyNotesConfig, readDailyNotesFileConfig } from "./daily-notes.js" + +/** The folders that delete and move refuse to touch. + * - PROTECTED_PATHS, when set, replaces the defaults entirely, so only the + * folders it lists are protected. + * - Otherwise the memory dir (protected even when MEMORY_ENABLED is false) + * plus the daily notes folder, resolved on each call (DAILY_NOTES_FOLDER → + * .obsidian/daily-notes.json → "Daily Notes") so a folder configured only + * in the vault is protected too. + * Throws when daily-notes.json exists but cannot be read, since the folder + * it names is then unknown; DAILY_NOTES_FOLDER or PROTECTED_PATHS bypasses + * the file. */ +export const resolveEffectiveProtectedPaths = async ( + { config, vaultPath }: { config: VaultConfig; vaultPath: string }, + logger: Logger, +): Promise => { + if (config.protectedPathsOverride) return config.protectedPathsOverride + + // loadConfig trims DAILY_NOTES_FOLDER, so a set value is never blank. + if (config.dailyNotesFolder) return [config.memoryDir, config.dailyNotesFolder] + + const dailyNotesConfig = await readDailyNotesFileConfig(vaultPath, logger) + + // A whitespace-only folder in daily-notes.json protects nothing. + const dailyFolder = dailyNotesConfig.folder.trim() + return dailyFolder ? [config.memoryDir, dailyFolder] : [config.memoryDir] +} + +export const resolveEffectiveOrphanExcludeFolders = ({ + orphanExcludeFoldersOverride, + memoryDir, + dailyNotesFolder, +}: { + orphanExcludeFoldersOverride: readonly string[] | null + memoryDir: string + dailyNotesFolder: string +}): readonly string[] => { + if (orphanExcludeFoldersOverride) return orphanExcludeFoldersOverride + + return dailyNotesFolder.trim() + ? [dailyNotesFolder, "Templates", memoryDir] + : ["Templates", memoryDir] +} + +/** Only the daily folder affects orphan exclusions, so its env override skips file I/O. */ +export const readEffectiveOrphanExcludeFolders = async ( + { config, vaultPath }: { config: VaultConfig; vaultPath: string }, + logger: Logger, +): Promise => { + if (config.orphanExcludeFoldersOverride) return config.orphanExcludeFoldersOverride + + if (config.dailyNotesFolder) { + return resolveEffectiveOrphanExcludeFolders({ + orphanExcludeFoldersOverride: null, + memoryDir: config.memoryDir, + dailyNotesFolder: config.dailyNotesFolder, + }) + } + + const dailyNotesConfig = await readDailyNotesConfig( + { vaultPath, envSettings: { format: config.dailyNotesFormat } }, + logger, + ) + return resolveEffectiveOrphanExcludeFolders({ + orphanExcludeFoldersOverride: null, + memoryDir: config.memoryDir, + dailyNotesFolder: dailyNotesConfig.folder, + }) +} From d373bb8efb0885fd67b374cdddd7e6a1749e5d29 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:28:18 -0400 Subject: [PATCH 02/16] fix(review): describe orphan exclusions accurately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: pr-review · gpt-6.1-sol --- .../__tests__/vault-orientation-prompt.test.ts | 11 ++++++++++- .../mcp-core/prompts/vault-orientation-prompt.ts | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts b/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts index 436f5cd85..27d6dcd95 100644 --- a/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts +++ b/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts @@ -93,6 +93,14 @@ describe("vault-orientation live orphan folders", () => { ) }) + it("describes an empty filtered result without claiming excluded notes are linked", async () => { + const { orphanSection } = await setupOrphanPrompt({ + settings: '{"folder":"Journal"}', + paths: ["Journal/daily.md"], + }) + expect(await orphanSection()).toBe("No orphans found after folder exclusions.") + }) + it("lets explicit environment folders replace all defaults", async () => { const { orphanSection } = await setupOrphanPrompt({ settings: '{"folder":"Journal"}', @@ -311,7 +319,8 @@ describe("vault-orientation handler", () => { const handler = findCall(calls, PROMPT_NAMES.VAULT_ORIENTATION)[2] const text = textOf(await handler(fakeExtra)) - expect(text).toContain("No orphans found — every note has at least one incoming link.") + const orphanSection = text.split("## Orphans\n")[1]?.split("\n\n## Memory")[0] + expect(orphanSection).toBe("No orphans found after folder exclusions.") }) it("shows property adoption rates with count/total format", async () => { diff --git a/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts b/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts index a636e45c5..549a38d46 100644 --- a/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts +++ b/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts @@ -234,7 +234,7 @@ export const registerVaultOrientationPrompt = ({ `${orphanCountLabel} orphan notes (no incoming links):`, ...orphans.map(formatNoteLine), ].join("\n") - : "No orphans found — every note has at least one incoming link." + : "No orphans found after folder exclusions." const memorySectionContent = memoryFiles.length > 0 From 78a7efb068a08a92b82074a334c911d250a6b1ae Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:42:05 -0400 Subject: [PATCH 03/16] docs: clarify orphan defaults and settings refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: fresh-eyes · gpt-6.1-sol --- .env.example | 5 +++-- ARCHITECTURE.md | 5 ++++- DEPLOY.md | 2 +- DOCKERHUB.md | 2 +- README.md | 5 ++++- cli/src/env.ts | 18 ++++++++++++------ deploy/local/.env.example | 9 ++++++--- deploy/remote/.env.example | 9 ++++++--- docker-compose.yml | 5 +++-- 9 files changed, 40 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index 6818e9913..54fed27ad 100644 --- a/.env.example +++ b/.env.example @@ -234,9 +234,10 @@ GHCR_USER= # PROTECTED_PATHS=About Me,Daily Notes # Comma-separated folders excluded from orphan detection. # Default: the daily notes folder, Templates, and MEMORY_DIR. -# The daily folder is read on each query from DAILY_NOTES_FOLDER or -# .obsidian/daily-notes.json, falling back to "Daily Notes". +# DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is reread +# on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. +# Apply env changes by redeploying; synced Obsidian settings apply on the next query. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata (default: https://github.com/aliasunder/vault-cortex) # SERVICE_DOCUMENTATION_URL=https://github.com/youruser/your-fork diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 92e1aa405..d66daef71 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -288,7 +288,10 @@ Link queries use a `links` table populated during indexing: 3. Basename (shortest-path-first for ambiguous basenames) - **Non-markdown files:** Targets that don't resolve to a note are checked against a `non_md_files` table (populated during rebuild, maintained by the file watcher). Both wikilinks and markdown-style links to `.canvas`, `.base`, images, PDFs, and other non-markdown files resolve as `kind: "file"` instead of being counted as broken. - **Outgoing links:** `vault_get_outgoing_links` returns a `kind` discriminator (`"note"` or `"file"`) plus each target's byte size (`bytes` — from the notes table for notes, from `non_md_files` for files), so clients can route notes to `vault_read_note` and files to `vault_read_file` with size awareness. -- **Orphans:** `vault_find_orphans` and `vault-orientation` resolve default exclusions on each invocation: the daily notes folder (`DAILY_NOTES_FOLDER` → `.obsidian/daily-notes.json` → `Daily Notes`), `Templates`, and the memory dir. `ORPHAN_EXCLUDE_FOLDERS` replaces that list; a tool call's `exclude_folders` replaces it for that call. +- **Orphans:** `vault_find_orphans` and `vault-orientation` resolve default exclusions on each invocation: the daily notes folder (`DAILY_NOTES_FOLDER` → `.obsidian/daily-notes.json` → `Daily Notes`), `Templates`, and the memory dir. + - `ORPHAN_EXCLUDE_FOLDERS` replaces that list; a tool call's `exclude_folders` replaces it for that call. + - An unreadable or malformed config logs a warning and uses `Daily Notes`; a missing file uses that fallback without a warning. + - An empty Obsidian folder setting uses the `Daily Notes` fallback. A whitespace-only folder adds no daily-folder exclusion. Neither excludes the whole vault, so root-level daily notes remain eligible. ### Files diff --git a/DEPLOY.md b/DEPLOY.md index 505dafd24..b9800572f 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -365,7 +365,7 @@ To find your stage: `cat .sst/stage` (after one-time setup). | `DISABLED_TOOLS` | Optional. Hide individual tools by name, comma-separated. Names match the Tool column in the [README tools table](./README.md#tools). Subtractive only; an unknown tool name stops the server at startup. Default: none hidden. | | `MEMORY_DIR` | Optional. Memory folder name in the vault (default: `About Me`). See the [Configuration](./README.md#configuration) section. | | `PROTECTED_PATHS` | Optional. Comma-separated folders protected from deletion and moves (default: `MEMORY_DIR` plus the daily notes folder, read from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json`, default `Daily Notes`). Overrides the default entirely when set. | -| `ORPHAN_EXCLUDE_FOLDERS` | Optional. Comma-separated folders excluded from orphan detection (default: the daily notes folder, `Templates`, and `MEMORY_DIR`). The daily notes folder is read on each query from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | +| `ORPHAN_EXCLUDE_FOLDERS` | Optional. Comma-separated folders excluded from orphan detection (default: the daily notes folder, `Templates`, and `MEMORY_DIR`). The `DAILY_NOTES_FOLDER` variable wins; otherwise synced `.obsidian/daily-notes.json` is reread per query (fallback `Daily Notes`). Overrides the default entirely when set. Apply variable changes with Force Deploy; synced Obsidian changes need no redeploy. See [Daily notes](./README.md#daily-notes) for root-level notes. | | `SERVICE_DOCUMENTATION_URL` | Optional. URL in OAuth discovery metadata (default: `https://github.com/aliasunder/vault-cortex`). Set to your fork's URL. | | `SYNC_CONFIGS` | Optional. Obsidian settings categories synced to the server, comma-separated (default: `core-plugin-data,community-plugin-data` — daily-notes settings and the Tasks plugin's format). Set `none` to disable. See [Daily notes](./README.md#daily-notes). | | `SYNC_EXCLUDED_FOLDERS` | Optional. Folders to leave out of Obsidian Sync, comma-separated — the same list as Obsidian's Sync → Excluded folders. Unset syncs everything. | diff --git a/DOCKERHUB.md b/DOCKERHUB.md index 79d2416fc..cc51ce1dc 100644 --- a/DOCKERHUB.md +++ b/DOCKERHUB.md @@ -167,7 +167,7 @@ All settings are environment variables with sensible defaults. Some defaults der | `DISABLED_TOOLS` | — | — | Hide individual tools by name, comma-separated (e.g. `vault_delete_note,vault_move_note`). Names match the Tool column in the [tools table](https://github.com/aliasunder/vault-cortex#tools). Subtractive only — it cannot re-enable a tool another setting hides. An unknown tool name stops the server at startup, so typos surface immediately. | | `MEMORY_DIR` | — | `About Me` | Vault folder for structured memory files | | `PROTECTED_PATHS` | — | `MEMORY_DIR`, daily notes folder | Folders that `vault_delete_note` and `vault_move_note` refuse to touch. The default daily notes folder is read from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | -| `ORPHAN_EXCLUDE_FOLDERS` | — | daily notes folder, `Templates`, `MEMORY_DIR` | Folders excluded from orphan detection. The default daily notes folder is read on each query from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | +| `ORPHAN_EXCLUDE_FOLDERS` | — | daily notes folder, `Templates`, `MEMORY_DIR` | Folders excluded from orphan detection. `DAILY_NOTES_FOLDER` wins; otherwise `.obsidian/daily-notes.json` is reread per query (fallback `Daily Notes`). Set a list to replace all defaults. Root-level daily notes remain eligible. See [Daily notes](https://github.com/aliasunder/vault-cortex#daily-notes). | | `DAILY_NOTES_FOLDER` | — | from vault config | Sets the folder your daily notes live in. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `Daily Notes`. See [Daily notes](https://github.com/aliasunder/vault-cortex#daily-notes). | | `DAILY_NOTES_FORMAT` | — | from vault config | Sets the daily note filename format — same tokens as Obsidian's daily note date format setting. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `YYYY-MM-DD`. See [Daily notes](https://github.com/aliasunder/vault-cortex#daily-notes). | | `TZ` | — | `UTC` | IANA timezone for timestamps and daily note resolution | diff --git a/README.md b/README.md index 39e89abe9..c0c777e10 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ All settings are environment variables with sensible defaults. Some defaults der | `DISABLED_TOOLS` | — | — | Hide individual tools by name, comma-separated (e.g. `vault_delete_note,vault_move_note`). Names match the Tool column in the [tools table](#tools). Subtractive only — it cannot re-enable a tool another setting hides. An unknown tool name stops the server at startup, so typos surface immediately. | | `MEMORY_DIR` | — | `About Me` | Vault folder for structured memory files | | `PROTECTED_PATHS` | — | `MEMORY_DIR`, daily notes folder | Folders that `vault_delete_note` and `vault_move_note` refuse to touch. The default daily notes folder is read from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | -| `ORPHAN_EXCLUDE_FOLDERS` | — | daily notes folder, `Templates`, `MEMORY_DIR` | Folders excluded from orphan detection. The default daily notes folder is read on each query from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | +| `ORPHAN_EXCLUDE_FOLDERS` | — | daily notes folder, `Templates`, `MEMORY_DIR` | Folders excluded from orphan detection. `DAILY_NOTES_FOLDER` wins; otherwise `.obsidian/daily-notes.json` is reread per query (fallback `Daily Notes`). Set a list to replace all defaults. Root-level daily notes remain eligible. See [Daily notes](#daily-notes). | | `DAILY_NOTES_FOLDER` | — | from vault config | Sets the folder your daily notes live in. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `Daily Notes`. See [Daily notes](#daily-notes). | | `DAILY_NOTES_FORMAT` | — | from vault config | Sets the daily note filename format — same tokens as Obsidian's daily note date format setting. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `YYYY-MM-DD`. See [Daily notes](#daily-notes). | | `TZ` | — | `UTC` | IANA timezone for timestamps and daily note resolution | @@ -420,6 +420,9 @@ When the file isn't available — or if you use the Periodic Notes plugin, whose You can set one or both — a set value always wins over the config file. Without either source, the server falls back to `Daily Notes` and `YYYY-MM-DD`. +- Changes in Obsidian apply on the next call once `.obsidian/daily-notes.json` reaches the server. Apply environment changes with the CLI's `restart` command, recreate the Compose container, or redeploy on your hosting platform. +- An empty Obsidian folder setting uses the server's `Daily Notes` fallback. Daily notes stored at the vault root remain eligible for orphan results; the server never excludes the whole vault automatically. + > **Note:** A few date format tokens are unsupported — ordinals (`Do`, `Mo`, `DDDo`, `wo`), `dd` (2-letter weekday), `d` (weekday number), `e`, `k`/`kk`, `w` (week number), `Q` (quarter), `Z`/`ZZ` (UTC offset), and the localized formats (`L`–`LLLL`, `LT`, `LTS`). The server can't reproduce the filenames Obsidian creates with these tokens, so it could never find the notes. If your format uses any of them, `vault_get_daily_note` returns a clear error — change the format in Obsidian or set `DAILY_NOTES_FORMAT` to a supported alternative. --- diff --git a/cli/src/env.ts b/cli/src/env.ts index eb5fc0ea5..ee56bfc18 100644 --- a/cli/src/env.ts +++ b/cli/src/env.ts @@ -78,7 +78,8 @@ EMBEDDING_ENABLED=true RERANK_MODE=blended # Enable or disable the memory layer (default: true). -# Set to false to hide memory tools and skip About Me/ creation. +# Set to false to hide memory tools and skip memory-folder creation. +# MEMORY_DIR still supplies default protected folders and orphan exclusions when false. MEMORY_ENABLED=true # Enable or disable file tools — vault_read_file and vault_list_files (default: true). # Set to false when Obsidian Sync has attachment syncing disabled. @@ -110,9 +111,11 @@ MEMORY_DIR=About Me # Comma-separated folders excluded from orphan detection. # Default: the daily notes folder, Templates, and MEMORY_DIR. -# The daily folder is read on each query from DAILY_NOTES_FOLDER or -# .obsidian/daily-notes.json, falling back to "Daily Notes". +# DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is reread +# on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. +# Apply env changes with CLI restart or recreate the Compose container; +# Obsidian settings apply on the next query once daily-notes.json reaches the server. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata @@ -220,7 +223,8 @@ MAX_IMAGE_OUTPUT_BYTES=49152 MAX_PDF_RENDER_PAGES=5 # Enable or disable the memory layer (default: true). -# Set to false to hide memory tools and skip About Me/ creation. +# Set to false to hide memory tools and skip memory-folder creation. +# MEMORY_DIR still supplies default protected folders and orphan exclusions when false. MEMORY_ENABLED=true # Enable or disable file tools — vault_read_file and vault_list_files (default: true). # Set to false when Obsidian Sync has attachment syncing disabled. @@ -253,9 +257,11 @@ MEMORY_DIR=About Me # Comma-separated folders excluded from orphan detection. # Default: the daily notes folder, Templates, and MEMORY_DIR. -# The daily folder is read on each query from DAILY_NOTES_FOLDER or -# .obsidian/daily-notes.json, falling back to "Daily Notes". +# DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is reread +# on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. +# Apply env changes with CLI restart or recreate the Compose container; +# Obsidian settings apply on the next query once daily-notes.json reaches the server. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata diff --git a/deploy/local/.env.example b/deploy/local/.env.example index 46aa4f86d..f921b4ce3 100644 --- a/deploy/local/.env.example +++ b/deploy/local/.env.example @@ -65,7 +65,8 @@ EMBEDDING_ENABLED=true RERANK_MODE=blended # Enable or disable the memory layer (default: true). -# Set to false to hide memory tools and skip About Me/ creation. +# Set to false to hide memory tools and skip memory-folder creation. +# MEMORY_DIR still supplies default protected folders and orphan exclusions when false. MEMORY_ENABLED=true # Enable or disable file tools — vault_read_file and vault_list_files (default: true). # Set to false when Obsidian Sync has attachment syncing disabled. @@ -97,9 +98,11 @@ MEMORY_DIR=About Me # Comma-separated folders excluded from orphan detection. # Default: the daily notes folder, Templates, and MEMORY_DIR. -# The daily folder is read on each query from DAILY_NOTES_FOLDER or -# .obsidian/daily-notes.json, falling back to "Daily Notes". +# DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is reread +# on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. +# Apply env changes with CLI restart or recreate the Compose container; +# Obsidian settings apply on the next query once daily-notes.json reaches the server. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata diff --git a/deploy/remote/.env.example b/deploy/remote/.env.example index 96bb5fbcc..18dcacc2c 100644 --- a/deploy/remote/.env.example +++ b/deploy/remote/.env.example @@ -71,7 +71,8 @@ MAX_IMAGE_OUTPUT_BYTES=49152 MAX_PDF_RENDER_PAGES=5 # Enable or disable the memory layer (default: true). -# Set to false to hide memory tools and skip About Me/ creation. +# Set to false to hide memory tools and skip memory-folder creation. +# MEMORY_DIR still supplies default protected folders and orphan exclusions when false. MEMORY_ENABLED=true # Enable or disable file tools — vault_read_file and vault_list_files (default: true). # Set to false when Obsidian Sync has attachment syncing disabled. @@ -104,9 +105,11 @@ MEMORY_DIR=About Me # Comma-separated folders excluded from orphan detection. # Default: the daily notes folder, Templates, and MEMORY_DIR. -# The daily folder is read on each query from DAILY_NOTES_FOLDER or -# .obsidian/daily-notes.json, falling back to "Daily Notes". +# DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is reread +# on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. +# Apply env changes with CLI restart or recreate the Compose container; +# Obsidian settings apply on the next query once daily-notes.json reaches the server. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata diff --git a/docker-compose.yml b/docker-compose.yml index 5c724a4ff..6fc8b95ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,8 +31,8 @@ services: SYNC_MODE: ${SYNC_MODE:-bidirectional} SYNC_EXCLUDED_FOLDERS: ${SYNC_EXCLUDED_FOLDERS:-} SYNC_FILE_TYPES: ${SYNC_FILE_TYPES:-} - # Obsidian config-category sync (default: core plugin data, so the - # vault's daily-notes settings reach the server). "none" disables; + # Obsidian config-category sync (default: core and community plugin data, + # including daily-note settings and the Tasks plugin format). "none" disables; # the category values are listed in .env.example. SYNC_CONFIGS: ${SYNC_CONFIGS:-core-plugin-data,community-plugin-data} # --- MCP server (svc-vault-mcp) --- @@ -99,6 +99,7 @@ services: MEMORY_DIR: ${MEMORY_DIR:-About Me} # Left empty = the server applies the smart defaults listed below, where and # stand for the resolved values (defaults "About Me" and "Daily Notes"). + # A set folder list replaces its whole default. # The daily notes folder is resolved from DAILY_NOTES_FOLDER or # .obsidian/daily-notes.json, falling back to "Daily Notes". # PROTECTED_PATHS default: ", " From 5e5fd72c0c9878340204f0c532c4db609805335a Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:43:14 -0400 Subject: [PATCH 04/16] docs: correct the CLI template test reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: fresh-eyes · gpt-6.1-sol --- cli/src/env.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cli/src/env.ts b/cli/src/env.ts index ee56bfc18..c4bec137b 100644 --- a/cli/src/env.ts +++ b/cli/src/env.ts @@ -13,11 +13,8 @@ export type RemoteEnvAnswers = { vaultPassword?: string } -// Optional env blocks are synced from deploy//.env.example by -// npm run sync:cli-env-blocks. Edit the deploy/ files, then re-run the script. -// cli/src/templates.test.ts asserts the CLI optional block vars match the -// deploy/ .env.example optional vars, so a new var breaks CI until both -// surfaces carry it. +// Run npm run sync:cli-env-blocks after editing deploy//.env.example. +// cli/src/__tests__/templates.test.ts checks that both surfaces list the same optional vars. // ┌─────────────────────────────────────────────────────────────────────────┐ // │ GENERATED — do not edit between sync markers. │ From 20f00c90a36357ffdb6bdcbf76a76fce35b90a48 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 19:09:10 -0400 Subject: [PATCH 05/16] style: clarify live orphan folder defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: code-quality · gpt-6.1-sol --- src/vault-mcp/config.ts | 3 ++- .../__tests__/__snapshots__/tool-surface/default.json | 2 +- .../__snapshots__/tool-surface/disabled-tools.json | 2 +- .../__tests__/__snapshots__/tool-surface/embedding-off.json | 2 +- .../tool-surface/file-tools-off+embedding-off.json | 2 +- .../__snapshots__/tool-surface/file-tools-off.json | 2 +- .../tool-surface/memory-off+embedding-off.json | 2 +- .../memory-off+file-tools-off+embedding-off.json | 2 +- .../tool-surface/memory-off+file-tools-off.json | 2 +- .../__tests__/__snapshots__/tool-surface/memory-off.json | 2 +- .../__tests__/__snapshots__/tool-surface/obsidian-sync.json | 2 +- .../__snapshots__/tool-surface/readonly+embedding-off.json | 2 +- .../tool-surface/readonly+file-tools-off+embedding-off.json | 2 +- .../__snapshots__/tool-surface/readonly+file-tools-off.json | 2 +- .../tool-surface/readonly+memory-off+embedding-off.json | 2 +- .../readonly+memory-off+file-tools-off+embedding-off.json | 2 +- .../tool-surface/readonly+memory-off+file-tools-off.json | 2 +- .../__snapshots__/tool-surface/readonly+memory-off.json | 2 +- .../__tests__/__snapshots__/tool-surface/readonly.json | 2 +- src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts | 2 +- src/vault-mcp/mcp-core/tools/search-tools.ts | 2 +- src/vault-mcp/vault-operations/vault-folder-config.ts | 6 ++---- 22 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/vault-mcp/config.ts b/src/vault-mcp/config.ts index 817bdea64..b5b2d2076 100644 --- a/src/vault-mcp/config.ts +++ b/src/vault-mcp/config.ts @@ -40,7 +40,7 @@ const parseVaultFolderList = (raw: string): string[] => /** Validates a DAILY_NOTES_FORMAT value by probe-rendering a fixed date. * Structural checks only — structurally unsafe results (traversal, - * separators, empty) are rejected. Warns when the format contains + * leading/trailing slashes, empty) are rejected. Warns when the format contains * unsupported tokens. Returns the raw moment string unchanged. */ const validateDailyNotesFormat = (momentFormat: string): string => { const renderedProbe = DateTime.fromISO("2026-01-31").toFormat(momentToLuxonFormat(momentFormat)) @@ -122,6 +122,7 @@ export type VaultConfig = Readonly<{ /** PROTECTED_PATHS as the user set it; null when unset, in which case the * protected set (memory dir + daily notes folder) is resolved per call. */ protectedPathsOverride: readonly string[] | null + /** Null uses per-call defaults; an empty list excludes no folders. */ orphanExcludeFoldersOverride: readonly string[] | null serviceDocumentationUrl: string /** When true, the embedding pipeline is active — notes are chunked, embedded diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json index 91a0ec8b7..f9930965e 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json @@ -272,7 +272,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json index c8b996e82..ac127127f 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json index de2d99f0b..8610c7dfc 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json index feb9a399f..494d86a08 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json @@ -275,7 +275,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json index 2df85c5f6..99ee2e08e 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json index ca7108792..40c941680 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json @@ -229,7 +229,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json index c4b79ce62..b30bbb78e 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json @@ -230,7 +230,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json index 7515c4097..7989a4389 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json @@ -229,7 +229,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json index 9d0bb2f65..632d4cd67 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json @@ -228,7 +228,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json index 2e40dd919..a857bd854 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json index 871fe0365..fed0c78a5 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json index bcad025a4..f48ab78ed 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json index 8ff0848a7..224a4d112 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json index 21a1a987e..cfc4606f3 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json index 157114586..0a1b32311 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json @@ -11,7 +11,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json index 66c547acd..11e4ff215 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json index a61c3c5ae..f2132ba9a 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json index 01d7d56e5..7251bf00a 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json @@ -8,7 +8,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts b/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts index 549a38d46..932b5e7ca 100644 --- a/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts +++ b/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts @@ -98,7 +98,7 @@ const formatMemoryOutline = (outlines: readonly MemoryFileOutline[]): string => outlines.map(formatMemoryOutlineEntry).join("\n") /** Formats the broken-link count for the stats line, including excluded - * forward-refs when present. Returns "" when there are no broken links. */ + * forward-refs when present. Returns "" when both counts are zero. */ const formatBrokenLinkSegment = (result: { count: number excludedFolder: string | null diff --git a/src/vault-mcp/mcp-core/tools/search-tools.ts b/src/vault-mcp/mcp-core/tools/search-tools.ts index 8914cd79f..327536f9a 100644 --- a/src/vault-mcp/mcp-core/tools/search-tools.ts +++ b/src/vault-mcp/mcp-core/tools/search-tools.ts @@ -613,7 +613,7 @@ Errors: const orphanDefaultDescription = config.orphanExcludeFoldersOverride ? `With exclude_folders omitted, the ORPHAN_EXCLUDE_FOLDERS override excludes ${JSON.stringify(config.orphanExcludeFoldersOverride)}.` - : `With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → "Daily Notes"), "Templates", and ${JSON.stringify(config.memoryDir)}, resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings warn and fall back.` + : `With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → "Daily Notes"), "Templates", and ${JSON.stringify(config.memoryDir)}, resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use "Daily Notes".` registerTool( TOOL_NAMES.VAULT_FIND_ORPHANS, diff --git a/src/vault-mcp/vault-operations/vault-folder-config.ts b/src/vault-mcp/vault-operations/vault-folder-config.ts index 3aaef9b93..13e2d88bb 100644 --- a/src/vault-mcp/vault-operations/vault-folder-config.ts +++ b/src/vault-mcp/vault-operations/vault-folder-config.ts @@ -39,6 +39,7 @@ export const resolveEffectiveOrphanExcludeFolders = ({ }): readonly string[] => { if (orphanExcludeFoldersOverride) return orphanExcludeFoldersOverride + /** Whitespace-only settings exclude no daily folder; spaces in a nonblank folder name stay. */ return dailyNotesFolder.trim() ? [dailyNotesFolder, "Templates", memoryDir] : ["Templates", memoryDir] @@ -59,10 +60,7 @@ export const readEffectiveOrphanExcludeFolders = async ( }) } - const dailyNotesConfig = await readDailyNotesConfig( - { vaultPath, envSettings: { format: config.dailyNotesFormat } }, - logger, - ) + const dailyNotesConfig = await readDailyNotesConfig({ vaultPath }, logger) return resolveEffectiveOrphanExcludeFolders({ orphanExcludeFoldersOverride: null, memoryDir: config.memoryDir, From f74dc4772fbd02064d1607a96bff34175d79a75d Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 19:10:34 -0400 Subject: [PATCH 06/16] style: name the server in orphan fallback guidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: code-quality · gpt-6.1-sol --- .../mcp-core/__tests__/__snapshots__/tool-surface/default.json | 2 +- .../__tests__/__snapshots__/tool-surface/disabled-tools.json | 2 +- .../__tests__/__snapshots__/tool-surface/embedding-off.json | 2 +- .../tool-surface/file-tools-off+embedding-off.json | 2 +- .../__tests__/__snapshots__/tool-surface/file-tools-off.json | 2 +- .../__snapshots__/tool-surface/memory-off+embedding-off.json | 2 +- .../tool-surface/memory-off+file-tools-off+embedding-off.json | 2 +- .../__snapshots__/tool-surface/memory-off+file-tools-off.json | 2 +- .../__tests__/__snapshots__/tool-surface/memory-off.json | 2 +- .../__tests__/__snapshots__/tool-surface/obsidian-sync.json | 2 +- .../__snapshots__/tool-surface/readonly+embedding-off.json | 2 +- .../tool-surface/readonly+file-tools-off+embedding-off.json | 2 +- .../__snapshots__/tool-surface/readonly+file-tools-off.json | 2 +- .../tool-surface/readonly+memory-off+embedding-off.json | 2 +- .../readonly+memory-off+file-tools-off+embedding-off.json | 2 +- .../tool-surface/readonly+memory-off+file-tools-off.json | 2 +- .../__snapshots__/tool-surface/readonly+memory-off.json | 2 +- .../mcp-core/__tests__/__snapshots__/tool-surface/readonly.json | 2 +- src/vault-mcp/mcp-core/tools/search-tools.ts | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json index f9930965e..adbe4f69b 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json @@ -272,7 +272,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json index ac127127f..892847ec4 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json index 8610c7dfc..e3bd452d6 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json index 494d86a08..8eebb254c 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json @@ -275,7 +275,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json index 99ee2e08e..1ecab404f 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json index 40c941680..e8b539019 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json @@ -229,7 +229,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json index b30bbb78e..c28ba92d9 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json @@ -230,7 +230,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json index 7989a4389..f95ef6706 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json @@ -229,7 +229,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json index 632d4cd67..51ae51107 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json @@ -228,7 +228,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json index a857bd854..040ff1ba4 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json index fed0c78a5..e3d4d11a2 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json index f48ab78ed..037735a92 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json index 224a4d112..b8d90fd3a 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json index cfc4606f3..172d571cf 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json index 0a1b32311..cc4eee99f 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json @@ -11,7 +11,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json index 11e4ff215..ab48f5fbf 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json index f2132ba9a..981e25342 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json index 7251bf00a..f35d37b28 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json @@ -8,7 +8,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/tools/search-tools.ts b/src/vault-mcp/mcp-core/tools/search-tools.ts index 327536f9a..2eebd746c 100644 --- a/src/vault-mcp/mcp-core/tools/search-tools.ts +++ b/src/vault-mcp/mcp-core/tools/search-tools.ts @@ -613,7 +613,7 @@ Errors: const orphanDefaultDescription = config.orphanExcludeFoldersOverride ? `With exclude_folders omitted, the ORPHAN_EXCLUDE_FOLDERS override excludes ${JSON.stringify(config.orphanExcludeFoldersOverride)}.` - : `With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → "Daily Notes"), "Templates", and ${JSON.stringify(config.memoryDir)}, resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. Unreadable daily settings log a server-side warning and use "Daily Notes".` + : `With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → "Daily Notes"), "Templates", and ${JSON.stringify(config.memoryDir)}, resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses "Daily Notes".` registerTool( TOOL_NAMES.VAULT_FIND_ORPHANS, From 79f5d911be42119aa9eea84b0f3f98dbd37acddb Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 19:36:32 -0400 Subject: [PATCH 07/16] test: verify orphan defaults across prompt queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: test-audit · gpt-6.1-sol --- src/vault-mcp/__tests__/config.test.ts | 5 +- .../__tests__/tool-definitions.test.ts | 40 ++++++++------- .../vault-orientation-prompt.test.ts | 51 +++++++++++++------ 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/src/vault-mcp/__tests__/config.test.ts b/src/vault-mcp/__tests__/config.test.ts index 0e2e913b6..003e57ccb 100644 --- a/src/vault-mcp/__tests__/config.test.ts +++ b/src/vault-mcp/__tests__/config.test.ts @@ -290,7 +290,10 @@ describe("loadConfig", () => { }) describe("ORPHAN_EXCLUDE_FOLDERS (comma-separated)", () => { - it.each(["", " "])("treats %j as no override", (input) => { + it.each([ + { label: "an empty value", input: "" }, + { label: "a whitespace-only value", input: " " }, + ])("treats $label as no override", ({ input }) => { expect(loadConfig({ ORPHAN_EXCLUDE_FOLDERS: input }).orphanExcludeFoldersOverride).toBeNull() }) diff --git a/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts b/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts index 88dedfd42..2c0134f0c 100644 --- a/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts +++ b/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts @@ -1522,23 +1522,29 @@ describe("vault_find_orphans live folder defaults", () => { ]) }) - it("returns every folder for request [] and bypasses malformed settings", async () => { - const { queryPaths, requestLogger } = await setupOrphans({ - settings: "broken", - env: { ORPHAN_EXCLUDE_FOLDERS: "Archive" }, - }) - expect(await queryPaths({ exclude_folders: [] })).toEqual([ - "Journal/daily.md", - "Journal/nested/daily.md", - "JournalOld/note.md", - "ordinary.md", - "Daily Notes/daily.md", - "Templates/template.md", - "About Me/memory.md", - "Archive/note.md", - ]) - expect(requestLogger.warn).not.toHaveBeenCalled() - }) + it.each([ + { label: "without an environment override", env: {} }, + { label: "over an environment override", env: { ORPHAN_EXCLUDE_FOLDERS: "Archive" } }, + ])( + "returns every folder for request [] $label and bypasses malformed settings", + async ({ env }) => { + const { queryPaths, requestLogger } = await setupOrphans({ + settings: "broken", + env, + }) + expect(await queryPaths({ exclude_folders: [] })).toEqual([ + "Journal/daily.md", + "Journal/nested/daily.md", + "JournalOld/note.md", + "ordinary.md", + "Daily Notes/daily.md", + "Templates/template.md", + "About Me/memory.md", + "Archive/note.md", + ]) + expect(requestLogger.warn).not.toHaveBeenCalled() + }, + ) it("uses a comma-only environment list as no exclusions", async () => { const { queryPaths, requestLogger } = await setupOrphans({ diff --git a/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts b/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts index 27d6dcd95..e2798c2ea 100644 --- a/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts +++ b/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts @@ -30,11 +30,15 @@ afterEach(() => { // ── vault-orientation handler ──────────────────────────────────── describe("vault-orientation live orphan folders", () => { + const dailyForwardReferences = + "[[Journal/2026-10-06]] [[Journal/2026-10-07]] [[Daily Notes/2026-10-06]] [[missing]]" + const setupOrphanPrompt = async ( options: { settings?: string env?: Record paths?: readonly string[] + noteContents?: Readonly> } = {}, ) => { const vault = await mkdtemp(join(tmpdir(), "orientation-orphans-")) @@ -56,8 +60,13 @@ describe("vault-orientation live orphan folders", () => { "Archive/note.md", ] paths.forEach((filePath, index) => { + const rawContent = options.noteContents?.[filePath] ?? "" search.upsertNote( - { filePath, rawContent: "", fileStat: { mtimeMs: 10000 - index, size: 0 } }, + { + filePath, + rawContent, + fileStat: { mtimeMs: 10000 - index, size: Buffer.byteLength(rawContent) }, + }, logger, ) }) @@ -65,14 +74,16 @@ describe("vault-orientation live orphan folders", () => { const config = loadConfig({ MEMORY_ENABLED: "false", MEMORY_DIR: "Profile", ...options.env }) const calls = registerWithSearch(vault, search, recordingLogger(logCalls), config) const handler = findCall(calls, PROMPT_NAMES.VAULT_ORIENTATION)[2] - const orphanSection = async () => { + const readSurveySections = async () => { const text = textOf(await handler(fakeExtra)) - const section = text.split("## Orphans\n")[1]?.split("\n\n---")[0]?.split("\n\n## ")[0] + const orphans = text.split("## Orphans\n")[1]?.split("\n\n---")[0]?.split("\n\n## ")[0] + const stats = text.split("## Vault stats\n")[1]?.split("\n\n## Folders")[0] - if (!section) throw new Error("prompt has no orphan section") - return section + if (!orphans || !stats) throw new Error("prompt has no orphan or stats section") + return { orphans, stats } } - return { settingsPath, orphanSection, logCalls, config } + const orphanSection = async () => (await readSurveySections()).orphans + return { vault, settingsPath, orphanSection, readSurveySections, logCalls, config } } it("excludes file-configured daily notes and descendants while retaining sibling folders", async () => { @@ -137,9 +148,10 @@ describe("vault-orientation live orphan folders", () => { }) it("honors an empty environment list and still reads daily settings once for broken links", async () => { - const { orphanSection, config } = await setupOrphanPrompt({ + const { vault, readSurveySections, config } = await setupOrphanPrompt({ settings: '{"folder":"Journal"}', env: { ORPHAN_EXCLUDE_FOLDERS: ", ," }, + noteContents: { "Journal/daily.md": dailyForwardReferences }, paths: [ "Journal/daily.md", "Daily Notes/daily.md", @@ -148,13 +160,16 @@ describe("vault-orientation live orphan folders", () => { ], }) vi.mocked(readDailyNotesConfig).mockClear() - expect(await orphanSection()).toBe( - "4 orphan notes (no incoming links):\n- Journal/daily.md — daily\n- Daily Notes/daily.md — daily\n- Templates/template.md — template\n- Profile/memory.md — memory", - ) + expect(await readSurveySections()).toEqual({ + orphans: + "4 orphan notes (no incoming links):\n- Journal/daily.md — daily\n- Daily Notes/daily.md — daily\n- Templates/template.md — template\n- Profile/memory.md — memory", + stats: + "4 notes across 0 folders, 0 tags, 0 property keys. 4 untagged. 4 without properties. 2 broken links (excludes 2 forward-refs in Journal/).", + }) expect(vi.mocked(readDailyNotesConfig)).toHaveBeenCalledTimes(1) expect(vi.mocked(readDailyNotesConfig)).toHaveBeenCalledWith( { - vaultPath: expect.any(String), + vaultPath: vault, envSettings: { folder: config.dailyNotesFolder, format: config.dailyNotesFormat }, }, expect.any(Object), @@ -162,11 +177,17 @@ describe("vault-orientation live orphan folders", () => { }) it("uses the same single daily settings read for default exclusions and broken links", async () => { - const { orphanSection } = await setupOrphanPrompt({ settings: '{"folder":"Journal"}' }) + const { readSurveySections } = await setupOrphanPrompt({ + settings: '{"folder":"Journal"}', + noteContents: { "ordinary.md": dailyForwardReferences }, + }) vi.mocked(readDailyNotesConfig).mockClear() - expect(await orphanSection()).toBe( - "4 orphan notes (no incoming links):\n- JournalOld/note.md — note\n- ordinary.md — ordinary\n- Daily Notes/daily.md — daily\n- Archive/note.md — note", - ) + expect(await readSurveySections()).toEqual({ + orphans: + "4 orphan notes (no incoming links):\n- JournalOld/note.md — note\n- ordinary.md — ordinary\n- Daily Notes/daily.md — daily\n- Archive/note.md — note", + stats: + "8 notes across 0 folders, 0 tags, 0 property keys. 8 untagged. 8 without properties. 2 broken links (excludes 2 forward-refs in Journal/).", + }) expect(vi.mocked(readDailyNotesConfig)).toHaveBeenCalledTimes(1) }) From 6bb89976426dddfbc7045a47894269dcdc0887d6 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 20:15:43 -0400 Subject: [PATCH 08/16] fix: preserve daily folder protection and orphan error contracts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: bug-check · gpt-6.1-sol --- AGENTS.md | 2 +- ARCHITECTURE.md | 2 +- .../server-error-contracts.test.ts | 23 +++++++++++++++++++ .../__snapshots__/tool-surface/default.json | 2 +- .../tool-surface/disabled-tools.json | 2 +- .../tool-surface/embedding-off.json | 2 +- .../file-tools-off+embedding-off.json | 2 +- .../tool-surface/file-tools-off.json | 2 +- .../memory-off+embedding-off.json | 2 +- ...mory-off+file-tools-off+embedding-off.json | 2 +- .../memory-off+file-tools-off.json | 2 +- .../tool-surface/memory-off.json | 2 +- .../tool-surface/obsidian-sync.json | 2 +- .../tool-surface/readonly+embedding-off.json | 2 +- ...readonly+file-tools-off+embedding-off.json | 2 +- .../tool-surface/readonly+file-tools-off.json | 2 +- .../readonly+memory-off+embedding-off.json | 2 +- ...mory-off+file-tools-off+embedding-off.json | 2 +- .../readonly+memory-off+file-tools-off.json | 2 +- .../tool-surface/readonly+memory-off.json | 2 +- .../__snapshots__/tool-surface/readonly.json | 2 +- src/vault-mcp/mcp-core/tools/search-tools.ts | 1 + .../__tests__/vault-folder-config.test.ts | 7 ++++++ .../vault-operations/vault-folder-config.ts | 4 ++-- 24 files changed, 53 insertions(+), 22 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b230f37aa..31b2d6862 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -147,7 +147,7 @@ src/ authorizer.ts # Lambda: path-aware auth (OAuth pass-through, JWT + static) vault-mcp/ server.ts # Entry point — config, mount routes, listen - config.ts # Env-var loader + ServerConfig type (loadConfig) + config.ts # Env-var loader + VaultConfig type (loadConfig) obsidian-markdown/ # Pure Obsidian/Markdown parsers + transforms (no I/O) lines.ts # splitIntoLines (CRLF) + fence state machine + classifyLines + pageTextByLines (line paging) frontmatter.ts # gray-matter parse/stringify + frontmatter merge diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d66daef71..eb1cd375b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1103,7 +1103,7 @@ The runtime image (`Dockerfile`) minimizes the attack surface: | Debian security fixes | `apt-get upgrade` at build time covers the node-image rebuild window | | Log rotation (Compose) | `max-size: 10m`, `max-file: 3` — prevents disk exhaustion | | Explicit proxy trust (Express) | `trust proxy` = `TRUST_PROXY_HOPS` (default 0 — direct exposure); the `Forwarded` header is honored only under a non-zero `TRUST_FORWARDED_HOPS` — injected forwarding headers can't spoof the client IP (OAuth rate-limit bucket key, request logs) | -| `Object.freeze` on config | Prevents accidental mutation of the loaded `ServerConfig` — defense against programming errors | +| `Object.freeze` on config | Prevents accidental mutation of the loaded `VaultConfig` — defense against programming errors | ### Durability diff --git a/src/__tests__/integration/server-error-contracts.test.ts b/src/__tests__/integration/server-error-contracts.test.ts index 75ab8a128..cf83b90f8 100644 --- a/src/__tests__/integration/server-error-contracts.test.ts +++ b/src/__tests__/integration/server-error-contracts.test.ts @@ -46,6 +46,29 @@ afterAll(async () => { } }) +// ── Orphan exclusion query capacity ────────────────────────── + +describe("orphan exclusion query capacity", () => { + it("returns a structured tool error when the exclusion list exceeds query capacity", async () => { + const result = await callTool({ + client, + name: "vault_find_orphans", + args: { + exclude_folders: Array.from({ length: 1000 }, (_, index) => `Folder${index}`), + }, + }) + expect(result).toEqual({ + content: [ + { + type: "text", + text: "[SqliteError]: Expression tree is too large (maximum depth 1000)", + }, + ], + isError: true, + }) + }) +}) + // ── Protected paths ────────────────────────────────────────── describe("protected path refusals", () => { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json index adbe4f69b..88a8062a1 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json @@ -272,7 +272,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json index 892847ec4..7d831d1c5 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json index e3bd452d6..ea8d05b39 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json index 8eebb254c..f74417ada 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json @@ -275,7 +275,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json index 1ecab404f..21f9cf704 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json index e8b539019..41a11993a 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json @@ -229,7 +229,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json index c28ba92d9..1650a8427 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json @@ -230,7 +230,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json index f95ef6706..e63a12b4f 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json @@ -229,7 +229,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json index 51ae51107..3035320b8 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json @@ -228,7 +228,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json index 040ff1ba4..fdf3173fb 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json index e3d4d11a2..b31573614 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json index 037735a92..5ff8aa3d4 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json index b8d90fd3a..598a70a3d 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json index 172d571cf..41688f66e 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json index cc4eee99f..5e51d5cf0 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json @@ -11,7 +11,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json index ab48f5fbf..a662dc743 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json index 981e25342..67a4741a3 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json index f35d37b28..8742d0a02 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json @@ -8,7 +8,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/tools/search-tools.ts b/src/vault-mcp/mcp-core/tools/search-tools.ts index 2eebd746c..32124e4ea 100644 --- a/src/vault-mcp/mcp-core/tools/search-tools.ts +++ b/src/vault-mcp/mcp-core/tools/search-tools.ts @@ -634,6 +634,7 @@ Parameters: Errors: - An empty array means no orphans were found (after exclusions), not an error. +- "Expression tree is too large" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry. Returns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.`, inputSchema: { diff --git a/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts b/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts index 0f180538f..18575a946 100644 --- a/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts +++ b/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts @@ -57,6 +57,13 @@ describe("resolveEffectiveProtectedPaths", () => { ).toEqual(["About Me"]) }) + it("preserves spaces in a nonblank file-configured daily folder", async () => { + const vaultPath = await createVault('{"folder":" Journal "}') + expect( + await resolveEffectiveProtectedPaths({ config: loadConfig({}), vaultPath }, logger), + ).toEqual(["About Me", " Journal "]) + }) + it("uses DAILY_NOTES_FOLDER without reading the file when the format is unset", async () => { const vaultPath = await createVault("broken") const requestLogger = { ...logger, warn: vi.fn() } diff --git a/src/vault-mcp/vault-operations/vault-folder-config.ts b/src/vault-mcp/vault-operations/vault-folder-config.ts index 13e2d88bb..e9db788df 100644 --- a/src/vault-mcp/vault-operations/vault-folder-config.ts +++ b/src/vault-mcp/vault-operations/vault-folder-config.ts @@ -24,8 +24,8 @@ export const resolveEffectiveProtectedPaths = async ( const dailyNotesConfig = await readDailyNotesFileConfig(vaultPath, logger) // A whitespace-only folder in daily-notes.json protects nothing. - const dailyFolder = dailyNotesConfig.folder.trim() - return dailyFolder ? [config.memoryDir, dailyFolder] : [config.memoryDir] + const dailyFolder = dailyNotesConfig.folder + return dailyFolder.trim() ? [config.memoryDir, dailyFolder] : [config.memoryDir] } export const resolveEffectiveOrphanExcludeFolders = ({ From 9486433aa76d0b623b21f514b4a15e2cc8f6c58c Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 20:23:58 -0400 Subject: [PATCH 09/16] fix: describe canvas references in orphan detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: bug-check · gpt-6.1-sol --- .../mcp-core/__tests__/__snapshots__/tool-surface/default.json | 2 +- .../__tests__/__snapshots__/tool-surface/disabled-tools.json | 2 +- .../__tests__/__snapshots__/tool-surface/embedding-off.json | 2 +- .../tool-surface/file-tools-off+embedding-off.json | 2 +- .../__tests__/__snapshots__/tool-surface/file-tools-off.json | 2 +- .../__snapshots__/tool-surface/memory-off+embedding-off.json | 2 +- .../tool-surface/memory-off+file-tools-off+embedding-off.json | 2 +- .../__snapshots__/tool-surface/memory-off+file-tools-off.json | 2 +- .../__tests__/__snapshots__/tool-surface/memory-off.json | 2 +- .../__tests__/__snapshots__/tool-surface/obsidian-sync.json | 2 +- .../__snapshots__/tool-surface/readonly+embedding-off.json | 2 +- .../tool-surface/readonly+file-tools-off+embedding-off.json | 2 +- .../__snapshots__/tool-surface/readonly+file-tools-off.json | 2 +- .../tool-surface/readonly+memory-off+embedding-off.json | 2 +- .../readonly+memory-off+file-tools-off+embedding-off.json | 2 +- .../tool-surface/readonly+memory-off+file-tools-off.json | 2 +- .../__snapshots__/tool-surface/readonly+memory-off.json | 2 +- .../mcp-core/__tests__/__snapshots__/tool-surface/readonly.json | 2 +- src/vault-mcp/mcp-core/tools/search-tools.ts | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json index 88a8062a1..0c798e94f 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json @@ -272,7 +272,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json index 7d831d1c5..8ddab0c97 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json index ea8d05b39..7719edc65 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json index f74417ada..718f831ae 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json @@ -275,7 +275,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json index 21f9cf704..9e65e91a0 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json index 41a11993a..1e0546066 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json @@ -229,7 +229,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json index 1650a8427..3844360ca 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json @@ -230,7 +230,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json index e63a12b4f..0f07be657 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json @@ -229,7 +229,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json index 3035320b8..33481e159 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json @@ -228,7 +228,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json index fdf3173fb..553d9f437 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json @@ -274,7 +274,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json index b31573614..7d360fe88 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json index 5ff8aa3d4..6f4e0e81f 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json index 598a70a3d..7795dfdbf 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json index 41688f66e..64305dcc0 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json index 5e51d5cf0..949a3850a 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json @@ -11,7 +11,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json index a662dc743..b018ee25c 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json @@ -10,7 +10,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json index 67a4741a3..da9ed740c 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json @@ -9,7 +9,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json index 8742d0a02..3d3c62744 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json @@ -8,7 +8,7 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/tools/search-tools.ts b/src/vault-mcp/mcp-core/tools/search-tools.ts index 32124e4ea..6e1ff7d6c 100644 --- a/src/vault-mcp/mcp-core/tools/search-tools.ts +++ b/src/vault-mcp/mcp-core/tools/search-tools.ts @@ -619,7 +619,7 @@ Errors: TOOL_NAMES.VAULT_FIND_ORPHANS, { title: "Find Orphans", - description: `Find notes with no incoming links from other notes — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored). + description: `Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored). Example: vault_find_orphans({}) Example: vault_find_orphans({ exclude_folders: ["Archive"], limit: 10 }) From da5b9bfa411819215d1e272f536cd435c2c8920d Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 20:26:41 -0400 Subject: [PATCH 10/16] docs: align orphan defaults in the MCP registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: pr-monitor · gpt-6.1-sol --- server.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.json b/server.json index 1045f9bfe..ad7b374f7 100644 --- a/server.json +++ b/server.json @@ -164,7 +164,7 @@ }, { "name": "ORPHAN_EXCLUDE_FOLDERS", - "description": "Comma-separated vault folder names excluded from vault_find_orphans. Default: DAILY_NOTES_FOLDER (else \"Daily Notes\"), \"Templates\", MEMORY_DIR." + "description": "Comma-separated vault folder names excluded from vault_find_orphans. Default: the daily notes folder, \"Templates\", and MEMORY_DIR. DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is read per query (fallback \"Daily Notes\"). When set, replaces the defaults entirely." }, { "name": "SERVICE_DOCUMENTATION_URL", From a791d8a319a1c491c59d9ac8004e106da8a01f38 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 20:57:33 -0400 Subject: [PATCH 11/16] docs: show how to clear default orphan exclusions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: pr-monitor · gpt-6.1-sol --- .env.example | 1 + DEPLOY.md | 2 +- DOCKERHUB.md | 4 +- README.md | 64 +++++++++++++------------- cli/src/env.ts | 2 + deploy/local/.env.example | 1 + deploy/remote/.env.example | 1 + server.json | 2 +- src/vault-mcp/__tests__/config.test.ts | 7 ++- 9 files changed, 46 insertions(+), 38 deletions(-) diff --git a/.env.example b/.env.example index 54fed27ad..c63738bef 100644 --- a/.env.example +++ b/.env.example @@ -237,6 +237,7 @@ GHCR_USER= # DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is reread # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. +# Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. # Apply env changes by redeploying; synced Obsidian settings apply on the next query. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata (default: https://github.com/aliasunder/vault-cortex) diff --git a/DEPLOY.md b/DEPLOY.md index b9800572f..f1e5d8780 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -365,7 +365,7 @@ To find your stage: `cat .sst/stage` (after one-time setup). | `DISABLED_TOOLS` | Optional. Hide individual tools by name, comma-separated. Names match the Tool column in the [README tools table](./README.md#tools). Subtractive only; an unknown tool name stops the server at startup. Default: none hidden. | | `MEMORY_DIR` | Optional. Memory folder name in the vault (default: `About Me`). See the [Configuration](./README.md#configuration) section. | | `PROTECTED_PATHS` | Optional. Comma-separated folders protected from deletion and moves (default: `MEMORY_DIR` plus the daily notes folder, read from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json`, default `Daily Notes`). Overrides the default entirely when set. | -| `ORPHAN_EXCLUDE_FOLDERS` | Optional. Comma-separated folders excluded from orphan detection (default: the daily notes folder, `Templates`, and `MEMORY_DIR`). The `DAILY_NOTES_FOLDER` variable wins; otherwise synced `.obsidian/daily-notes.json` is reread per query (fallback `Daily Notes`). Overrides the default entirely when set. Apply variable changes with Force Deploy; synced Obsidian changes need no redeploy. See [Daily notes](./README.md#daily-notes) for root-level notes. | +| `ORPHAN_EXCLUDE_FOLDERS` | Optional. Comma-separated folders excluded from orphan detection (default: the daily notes folder, `Templates`, and `MEMORY_DIR`). The `DAILY_NOTES_FOLDER` variable wins; otherwise synced `.obsidian/daily-notes.json` is reread per query (fallback `Daily Notes`). Overrides the default entirely when set. Use `,` to exclude nothing; an empty value uses the defaults. Apply variable changes with Force Deploy; synced Obsidian changes need no redeploy. See [Daily notes](./README.md#daily-notes) for root-level notes. | | `SERVICE_DOCUMENTATION_URL` | Optional. URL in OAuth discovery metadata (default: `https://github.com/aliasunder/vault-cortex`). Set to your fork's URL. | | `SYNC_CONFIGS` | Optional. Obsidian settings categories synced to the server, comma-separated (default: `core-plugin-data,community-plugin-data` — daily-notes settings and the Tasks plugin's format). Set `none` to disable. See [Daily notes](./README.md#daily-notes). | | `SYNC_EXCLUDED_FOLDERS` | Optional. Folders to leave out of Obsidian Sync, comma-separated — the same list as Obsidian's Sync → Excluded folders. Unset syncs everything. | diff --git a/DOCKERHUB.md b/DOCKERHUB.md index cc51ce1dc..c556d255e 100644 --- a/DOCKERHUB.md +++ b/DOCKERHUB.md @@ -151,7 +151,7 @@ Vault Cortex indexes every [property](https://help.obsidian.md/Editing+and+forma All settings are environment variables with sensible defaults. Some defaults derive from other settings — the Default column shows each derivation, and a value you set replaces the whole derived default. Remote deployments also forward Obsidian Sync's own settings — `DEVICE_NAME`, `SYNC_MODE`, `CONFLICT_STRATEGY`, `SYNC_CONFIGS`, `SYNC_EXCLUDED_FOLDERS`, `SYNC_FILE_TYPES` — documented in the [remote guide's configuration table](https://github.com/aliasunder/vault-cortex/tree/main/deploy/remote/README.md#configuration). | Variable | Required? | Default | Description | -| --------------------------- | ----------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| --------------------------- | ----------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MCP_AUTH_TOKEN` | Yes | — | Bearer token for authentication (also the JWT signing key) | | `VAULT_PATH` | Local only | — | Host path to your vault (bind mount source; remote uses a named volume). Must not contain `*`, `?`, or `[` — rejected at startup. | | `PUBLIC_URL` | Remote only | — | Public URL for OAuth discovery metadata. Filled in automatically on Render and Railway (from `RENDER_EXTERNAL_URL` or `RAILWAY_PUBLIC_DOMAIN`) when left unset | @@ -167,7 +167,7 @@ All settings are environment variables with sensible defaults. Some defaults der | `DISABLED_TOOLS` | — | — | Hide individual tools by name, comma-separated (e.g. `vault_delete_note,vault_move_note`). Names match the Tool column in the [tools table](https://github.com/aliasunder/vault-cortex#tools). Subtractive only — it cannot re-enable a tool another setting hides. An unknown tool name stops the server at startup, so typos surface immediately. | | `MEMORY_DIR` | — | `About Me` | Vault folder for structured memory files | | `PROTECTED_PATHS` | — | `MEMORY_DIR`, daily notes folder | Folders that `vault_delete_note` and `vault_move_note` refuse to touch. The default daily notes folder is read from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | -| `ORPHAN_EXCLUDE_FOLDERS` | — | daily notes folder, `Templates`, `MEMORY_DIR` | Folders excluded from orphan detection. `DAILY_NOTES_FOLDER` wins; otherwise `.obsidian/daily-notes.json` is reread per query (fallback `Daily Notes`). Set a list to replace all defaults. Root-level daily notes remain eligible. See [Daily notes](https://github.com/aliasunder/vault-cortex#daily-notes). | +| `ORPHAN_EXCLUDE_FOLDERS` | — | daily notes folder, `Templates`, `MEMORY_DIR` | Folders excluded from orphan detection. `DAILY_NOTES_FOLDER` wins; otherwise `.obsidian/daily-notes.json` is reread per query (fallback `Daily Notes`). Set a list to replace all defaults, or `ORPHAN_EXCLUDE_FOLDERS=,` to exclude nothing. An empty value uses the defaults. Root-level daily notes remain eligible. See [Daily notes](https://github.com/aliasunder/vault-cortex#daily-notes). | | `DAILY_NOTES_FOLDER` | — | from vault config | Sets the folder your daily notes live in. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `Daily Notes`. See [Daily notes](https://github.com/aliasunder/vault-cortex#daily-notes). | | `DAILY_NOTES_FORMAT` | — | from vault config | Sets the daily note filename format — same tokens as Obsidian's daily note date format setting. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `YYYY-MM-DD`. See [Daily notes](https://github.com/aliasunder/vault-cortex#daily-notes). | | `TZ` | — | `UTC` | IANA timezone for timestamps and daily note resolution | diff --git a/README.md b/README.md index c0c777e10..dc9aef240 100644 --- a/README.md +++ b/README.md @@ -371,38 +371,38 @@ These are conventions, not requirements — Vault Cortex works with any property All settings are environment variables with sensible defaults. Some defaults derive from other settings — the Default column shows each derivation, and a value you set replaces the whole derived default. Remote deployments also forward Obsidian Sync's own settings — `DEVICE_NAME`, `SYNC_MODE`, `CONFLICT_STRATEGY`, `SYNC_CONFIGS`, `SYNC_EXCLUDED_FOLDERS`, `SYNC_FILE_TYPES` — documented in the [remote guide's configuration table](./deploy/remote/README.md#configuration). -| Variable | Required? | Default | Description | -| --------------------------- | ----------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MCP_AUTH_TOKEN` | Yes | — | Bearer token for authentication (also the JWT signing key) | -| `VAULT_PATH` | Local only | — | Host path to your vault (bind mount source; remote uses a named volume). Must not contain `*`, `?`, or `[` — rejected at startup. | -| `PUBLIC_URL` | Remote only | — | Public URL for OAuth discovery metadata. Filled in automatically on Render and Railway (from `RENDER_EXTERNAL_URL` or `RAILWAY_PUBLIC_DOMAIN`) when left unset | -| `OBSIDIAN_AUTH_TOKEN` | — | — | Obsidian Sync auth token. Leave empty to sign in through the `/setup` page after deploy; or the CLI's [`get-sync-token`](./cli/README.md#get-sync-token) captures it for you | -| `VAULT_NAME` | Remote only | — | Exact name of your Obsidian vault (case-sensitive) | -| `VAULT_PASSWORD` | Remote only | — | End-to-end encryption password, if your vault has one. Leave empty otherwise. | -| `STORAGE_ROOT` | — | — | One directory for everything that must persist — the vault, the search index, and Obsidian Sync state — for container hosting platforms that allow a single persistent volume (Railway, Render). Mount the volume there and set this to the same path. Must not contain `*`, `?`, or `[` — rejected at startup. | -| `EMBEDDING_ENABLED` | — | `true` | Set `false` to disable the embedding pipeline — skips model download, vector tables, embedding passes, and hybrid search. Search falls back to FTS5 keyword matching. | -| `RERANK_MODE` | — | `blended` | Cross-encoder reranking mode: `blended` applies position-aware score blending after RRF fusion (~200ms added latency), `none` skips reranking. Only takes effect when `EMBEDDING_ENABLED` is true. | -| `MEMORY_ENABLED` | — | `true` | Set `false` to fully disable the memory layer — hides memory tools, skips bootstrap, omits memory from server metadata. `MEMORY_DIR` still supplies the defaults for `PROTECTED_PATHS` and `ORPHAN_EXCLUDE_FOLDERS` when `false`. | -| `FILE_TOOLS_ENABLED` | — | `true` | Set `false` to hide file tools (`vault_read_file`, `vault_list_files`) — useful for remote deployments where Obsidian Sync has attachment syncing disabled. | -| `READONLY_MODE` | — | `false` | Set `true` to hide every tool that changes the vault and skip memory folder auto-creation — connected clients can read and search but never edit. | -| `DISABLED_TOOLS` | — | — | Hide individual tools by name, comma-separated (e.g. `vault_delete_note,vault_move_note`). Names match the Tool column in the [tools table](#tools). Subtractive only — it cannot re-enable a tool another setting hides. An unknown tool name stops the server at startup, so typos surface immediately. | -| `MEMORY_DIR` | — | `About Me` | Vault folder for structured memory files | -| `PROTECTED_PATHS` | — | `MEMORY_DIR`, daily notes folder | Folders that `vault_delete_note` and `vault_move_note` refuse to touch. The default daily notes folder is read from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | -| `ORPHAN_EXCLUDE_FOLDERS` | — | daily notes folder, `Templates`, `MEMORY_DIR` | Folders excluded from orphan detection. `DAILY_NOTES_FOLDER` wins; otherwise `.obsidian/daily-notes.json` is reread per query (fallback `Daily Notes`). Set a list to replace all defaults. Root-level daily notes remain eligible. See [Daily notes](#daily-notes). | -| `DAILY_NOTES_FOLDER` | — | from vault config | Sets the folder your daily notes live in. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `Daily Notes`. See [Daily notes](#daily-notes). | -| `DAILY_NOTES_FORMAT` | — | from vault config | Sets the daily note filename format — same tokens as Obsidian's daily note date format setting. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `YYYY-MM-DD`. See [Daily notes](#daily-notes). | -| `TZ` | — | `UTC` | IANA timezone for timestamps and daily note resolution | -| `SERVICE_DOCUMENTATION_URL` | — | GitHub repo URL | URL returned in OAuth discovery metadata | -| `LOG_LEVEL` | — | `info` | Logging verbosity: `debug`, `info`, `warn`, `error` | -| `LOG_DIR` | — | `/data/logs` (remote), `$STORAGE_ROOT/data/logs` (single-volume), `none` (local) | Directory for log files that survive container re-creation. The container's own log (what `docker logs` shows) is always written, but Docker discards it whenever the container is recreated — on image updates or config changes. Date-stamped files under `LOG_DIR` live on the data volume and survive. `none` keeps only the container log. | -| `LOG_RETENTION_DAYS` | — | `90` | Days to keep log files before automatic cleanup on startup; only applies when `LOG_DIR` is a path | -| `WINDOWS_MODE` | — | `false` | On Windows? Set `true`. Switches the file watcher to polling and note moves to rename-based writes so a vault on a `C:` drive works through Docker Desktop. Safe to leave on for any Windows setup; unneeded on macOS/Linux/WSL2. | -| `MAX_FILE_BYTES` | — | `52428800` (50 MiB) | Maximum file size `vault_read_file` will read (in bytes). Files exceeding this are rejected before reading. Raise for vaults with very large individual files. | -| `MAX_IMAGE_OUTPUT_BYTES` | — | `49152` (48 KiB) | Byte budget for images delivered by `vault_read_file`, in binary bytes before base64 encoding. Images exceeding this are downscaled and recompressed to fit. Sized for the tightest mainstream MCP client cap; raise for clients that accept larger responses. | -| `MAX_PDF_RENDER_PAGES` | — | `5` | Maximum PDF pages to render as images when `raw: true` is set on `vault_read_file`. The per-page byte budget is `MAX_IMAGE_OUTPUT_BYTES` divided evenly across the rendered pages — fewer pages means higher quality each. | -| `TRASH_RETENTION_DAYS` | Local only | `30` | Days a note deleted under Obsidian's default "Move to system trash" setting stays in `.trash/` before the server cleans it up. Set `none` to keep those notes forever. Only notes the server itself moved there are cleaned up. With Obsidian Sync, deletes are permanent on the server and recoverable from Sync's version history. | -| `TRUST_PROXY_HOPS` | — | `0` | Number of trusted reverse-proxy hops used to derive the client IP from `X-Forwarded-For` (OAuth rate limiting, request logs). Set `1` when exactly one proxy you control sits in front of the server (Caddy, nginx, Cloudflare Tunnel, API Gateway). With `0`, injected forwarding headers are ignored. | -| `TRUST_FORWARDED_HOPS` | — | `0` | How many trailing `for=` entries in the [RFC 7239](https://www.rfc-editor.org/rfc/rfc7239) `Forwarded` header belong to proxies you control. `0` ignores the header; `1` when the proxy in front writes it (e.g. AWS API Gateway); `2` when a CDN fronts that proxy and is the only way to reach it. | +| Variable | Required? | Default | Description | +| --------------------------- | ----------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MCP_AUTH_TOKEN` | Yes | — | Bearer token for authentication (also the JWT signing key) | +| `VAULT_PATH` | Local only | — | Host path to your vault (bind mount source; remote uses a named volume). Must not contain `*`, `?`, or `[` — rejected at startup. | +| `PUBLIC_URL` | Remote only | — | Public URL for OAuth discovery metadata. Filled in automatically on Render and Railway (from `RENDER_EXTERNAL_URL` or `RAILWAY_PUBLIC_DOMAIN`) when left unset | +| `OBSIDIAN_AUTH_TOKEN` | — | — | Obsidian Sync auth token. Leave empty to sign in through the `/setup` page after deploy; or the CLI's [`get-sync-token`](./cli/README.md#get-sync-token) captures it for you | +| `VAULT_NAME` | Remote only | — | Exact name of your Obsidian vault (case-sensitive) | +| `VAULT_PASSWORD` | Remote only | — | End-to-end encryption password, if your vault has one. Leave empty otherwise. | +| `STORAGE_ROOT` | — | — | One directory for everything that must persist — the vault, the search index, and Obsidian Sync state — for container hosting platforms that allow a single persistent volume (Railway, Render). Mount the volume there and set this to the same path. Must not contain `*`, `?`, or `[` — rejected at startup. | +| `EMBEDDING_ENABLED` | — | `true` | Set `false` to disable the embedding pipeline — skips model download, vector tables, embedding passes, and hybrid search. Search falls back to FTS5 keyword matching. | +| `RERANK_MODE` | — | `blended` | Cross-encoder reranking mode: `blended` applies position-aware score blending after RRF fusion (~200ms added latency), `none` skips reranking. Only takes effect when `EMBEDDING_ENABLED` is true. | +| `MEMORY_ENABLED` | — | `true` | Set `false` to fully disable the memory layer — hides memory tools, skips bootstrap, omits memory from server metadata. `MEMORY_DIR` still supplies the defaults for `PROTECTED_PATHS` and `ORPHAN_EXCLUDE_FOLDERS` when `false`. | +| `FILE_TOOLS_ENABLED` | — | `true` | Set `false` to hide file tools (`vault_read_file`, `vault_list_files`) — useful for remote deployments where Obsidian Sync has attachment syncing disabled. | +| `READONLY_MODE` | — | `false` | Set `true` to hide every tool that changes the vault and skip memory folder auto-creation — connected clients can read and search but never edit. | +| `DISABLED_TOOLS` | — | — | Hide individual tools by name, comma-separated (e.g. `vault_delete_note,vault_move_note`). Names match the Tool column in the [tools table](#tools). Subtractive only — it cannot re-enable a tool another setting hides. An unknown tool name stops the server at startup, so typos surface immediately. | +| `MEMORY_DIR` | — | `About Me` | Vault folder for structured memory files | +| `PROTECTED_PATHS` | — | `MEMORY_DIR`, daily notes folder | Folders that `vault_delete_note` and `vault_move_note` refuse to touch. The default daily notes folder is read from `DAILY_NOTES_FOLDER` or `.obsidian/daily-notes.json` (default `Daily Notes`). Overrides the default entirely when set. | +| `ORPHAN_EXCLUDE_FOLDERS` | — | daily notes folder, `Templates`, `MEMORY_DIR` | Folders excluded from orphan detection. `DAILY_NOTES_FOLDER` wins; otherwise `.obsidian/daily-notes.json` is reread per query (fallback `Daily Notes`). Set a list to replace all defaults, or `ORPHAN_EXCLUDE_FOLDERS=,` to exclude nothing. An empty value uses the defaults. Root-level daily notes remain eligible. See [Daily notes](#daily-notes). | +| `DAILY_NOTES_FOLDER` | — | from vault config | Sets the folder your daily notes live in. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `Daily Notes`. See [Daily notes](#daily-notes). | +| `DAILY_NOTES_FORMAT` | — | from vault config | Sets the daily note filename format — same tokens as Obsidian's daily note date format setting. When unset, read from the vault's `.obsidian/daily-notes.json`, falling back to `YYYY-MM-DD`. See [Daily notes](#daily-notes). | +| `TZ` | — | `UTC` | IANA timezone for timestamps and daily note resolution | +| `SERVICE_DOCUMENTATION_URL` | — | GitHub repo URL | URL returned in OAuth discovery metadata | +| `LOG_LEVEL` | — | `info` | Logging verbosity: `debug`, `info`, `warn`, `error` | +| `LOG_DIR` | — | `/data/logs` (remote), `$STORAGE_ROOT/data/logs` (single-volume), `none` (local) | Directory for log files that survive container re-creation. The container's own log (what `docker logs` shows) is always written, but Docker discards it whenever the container is recreated — on image updates or config changes. Date-stamped files under `LOG_DIR` live on the data volume and survive. `none` keeps only the container log. | +| `LOG_RETENTION_DAYS` | — | `90` | Days to keep log files before automatic cleanup on startup; only applies when `LOG_DIR` is a path | +| `WINDOWS_MODE` | — | `false` | On Windows? Set `true`. Switches the file watcher to polling and note moves to rename-based writes so a vault on a `C:` drive works through Docker Desktop. Safe to leave on for any Windows setup; unneeded on macOS/Linux/WSL2. | +| `MAX_FILE_BYTES` | — | `52428800` (50 MiB) | Maximum file size `vault_read_file` will read (in bytes). Files exceeding this are rejected before reading. Raise for vaults with very large individual files. | +| `MAX_IMAGE_OUTPUT_BYTES` | — | `49152` (48 KiB) | Byte budget for images delivered by `vault_read_file`, in binary bytes before base64 encoding. Images exceeding this are downscaled and recompressed to fit. Sized for the tightest mainstream MCP client cap; raise for clients that accept larger responses. | +| `MAX_PDF_RENDER_PAGES` | — | `5` | Maximum PDF pages to render as images when `raw: true` is set on `vault_read_file`. The per-page byte budget is `MAX_IMAGE_OUTPUT_BYTES` divided evenly across the rendered pages — fewer pages means higher quality each. | +| `TRASH_RETENTION_DAYS` | Local only | `30` | Days a note deleted under Obsidian's default "Move to system trash" setting stays in `.trash/` before the server cleans it up. Set `none` to keep those notes forever. Only notes the server itself moved there are cleaned up. With Obsidian Sync, deletes are permanent on the server and recoverable from Sync's version history. | +| `TRUST_PROXY_HOPS` | — | `0` | Number of trusted reverse-proxy hops used to derive the client IP from `X-Forwarded-For` (OAuth rate limiting, request logs). Set `1` when exactly one proxy you control sits in front of the server (Caddy, nginx, Cloudflare Tunnel, API Gateway). With `0`, injected forwarding headers are ignored. | +| `TRUST_FORWARDED_HOPS` | — | `0` | How many trailing `for=` entries in the [RFC 7239](https://www.rfc-editor.org/rfc/rfc7239) `Forwarded` header belong to proxies you control. `0` ignores the header; `1` when the proxy in front writes it (e.g. AWS API Gateway); `2` when a CDN fronts that proxy and is the only way to reach it. | See [`templates/memory/`](./templates/memory/) for memory file examples and the dated-entry design philosophy. diff --git a/cli/src/env.ts b/cli/src/env.ts index c4bec137b..d847c1049 100644 --- a/cli/src/env.ts +++ b/cli/src/env.ts @@ -111,6 +111,7 @@ MEMORY_DIR=About Me # DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is reread # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. +# Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. # Apply env changes with CLI restart or recreate the Compose container; # Obsidian settings apply on the next query once daily-notes.json reaches the server. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me @@ -257,6 +258,7 @@ MEMORY_DIR=About Me # DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is reread # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. +# Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. # Apply env changes with CLI restart or recreate the Compose container; # Obsidian settings apply on the next query once daily-notes.json reaches the server. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me diff --git a/deploy/local/.env.example b/deploy/local/.env.example index f921b4ce3..98457c130 100644 --- a/deploy/local/.env.example +++ b/deploy/local/.env.example @@ -101,6 +101,7 @@ MEMORY_DIR=About Me # DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is reread # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. +# Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. # Apply env changes with CLI restart or recreate the Compose container; # Obsidian settings apply on the next query once daily-notes.json reaches the server. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me diff --git a/deploy/remote/.env.example b/deploy/remote/.env.example index 18dcacc2c..2f459acb2 100644 --- a/deploy/remote/.env.example +++ b/deploy/remote/.env.example @@ -108,6 +108,7 @@ MEMORY_DIR=About Me # DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is reread # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. +# Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. # Apply env changes with CLI restart or recreate the Compose container; # Obsidian settings apply on the next query once daily-notes.json reaches the server. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me diff --git a/server.json b/server.json index ad7b374f7..935272b6d 100644 --- a/server.json +++ b/server.json @@ -164,7 +164,7 @@ }, { "name": "ORPHAN_EXCLUDE_FOLDERS", - "description": "Comma-separated vault folder names excluded from vault_find_orphans. Default: the daily notes folder, \"Templates\", and MEMORY_DIR. DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is read per query (fallback \"Daily Notes\"). When set, replaces the defaults entirely." + "description": "Comma-separated vault folder names excluded from vault_find_orphans. Default: the daily notes folder, \"Templates\", and MEMORY_DIR. DAILY_NOTES_FOLDER wins; otherwise .obsidian/daily-notes.json is read per query (fallback \"Daily Notes\"). When set, replaces the defaults entirely. Set to a comma (,) to exclude nothing; an empty value uses the defaults." }, { "name": "SERVICE_DOCUMENTATION_URL", diff --git a/src/vault-mcp/__tests__/config.test.ts b/src/vault-mcp/__tests__/config.test.ts index 003e57ccb..517c60c5b 100644 --- a/src/vault-mcp/__tests__/config.test.ts +++ b/src/vault-mcp/__tests__/config.test.ts @@ -297,8 +297,11 @@ describe("loadConfig", () => { expect(loadConfig({ ORPHAN_EXCLUDE_FOLDERS: input }).orphanExcludeFoldersOverride).toBeNull() }) - it("preserves a comma-only explicit empty override", () => { - expect(loadConfig({ ORPHAN_EXCLUDE_FOLDERS: ", ," }).orphanExcludeFoldersOverride).toEqual([]) + it.each([ + { label: "a single comma", input: "," }, + { label: "commas separated by spaces", input: ", ," }, + ])("$label clears the orphan exclusions", ({ input }) => { + expect(loadConfig({ ORPHAN_EXCLUDE_FOLDERS: input }).orphanExcludeFoldersOverride).toEqual([]) }) it("overrides the default entirely", () => { From 6f23a2cee2cc3053d64f25e9303054da32e66918 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:43:11 -0400 Subject: [PATCH 12/16] fix: translate orphan query capacity failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: bug-check · gpt-6.1-sol --- .../server-error-contracts.test.ts | 2 +- .../search/__tests__/search-index.test.ts | 55 +++++++++++++++++++ src/vault-mcp/search/search-queries.ts | 20 ++++++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/__tests__/integration/server-error-contracts.test.ts b/src/__tests__/integration/server-error-contracts.test.ts index cf83b90f8..9cc00e662 100644 --- a/src/__tests__/integration/server-error-contracts.test.ts +++ b/src/__tests__/integration/server-error-contracts.test.ts @@ -61,7 +61,7 @@ describe("orphan exclusion query capacity", () => { content: [ { type: "text", - text: "[SqliteError]: Expression tree is too large (maximum depth 1000)", + text: "[Error]: too many excluded folders", }, ], isError: true, diff --git a/src/vault-mcp/search/__tests__/search-index.test.ts b/src/vault-mcp/search/__tests__/search-index.test.ts index ae4eab18a..ea9d9fbe8 100644 --- a/src/vault-mcp/search/__tests__/search-index.test.ts +++ b/src/vault-mcp/search/__tests__/search-index.test.ts @@ -3768,6 +3768,61 @@ describe("findOrphans", () => { expect(orphanPaths).toContain("Projects/orphan.md") }) + it("translates oversized exclusions while retaining and logging the SQLite diagnostic", () => { + const queryIndex = createSearchIndex(":memory:") + const requestLogger = { ...logger, warn: vi.fn() } + const excludeFolders = Array.from({ length: 1000 }, (_, folderIndex) => `Folder${folderIndex}`) + const queryError = (() => { + try { + queryIndex.findOrphans({ excludeFolders }, requestLogger) + } catch (error) { + return error + } + throw new Error("expected the orphan query to fail") + })() + + if (!(queryError instanceof Error) || !(queryError.cause instanceof Database.SqliteError)) { + throw new Error("expected a domain error with the original SQLite cause") + } + expect({ + message: queryError.message, + causeName: queryError.cause.name, + causeCode: queryError.cause.code, + causeMessage: queryError.cause.message, + }).toEqual({ + message: "too many excluded folders", + causeName: "SqliteError", + causeCode: "SQLITE_ERROR", + causeMessage: "Expression tree is too large (maximum depth 1000)", + }) + expect(requestLogger.warn).toHaveBeenCalledTimes(1) + expect(requestLogger.warn).toHaveBeenCalledWith("orphan exclusion query capacity exceeded", { + excludedFolderCount: 1000, + error: "[SqliteError]: Expression tree is too large (maximum depth 1000)", + }) + }) + + it("propagates unrelated SQLite query failures unchanged", () => { + const queryIndex = createSearchIndex(":memory:") + const requestLogger = { ...logger, warn: vi.fn() } + const sqliteError = new Database.SqliteError("no such table: notes", "SQLITE_ERROR") + const prepareSpy = vi.spyOn(Database.prototype, "prepare").mockImplementationOnce(() => { + throw sqliteError + }) + onTestFinished(() => prepareSpy.mockRestore()) + const queryError = (() => { + try { + queryIndex.findOrphans({}, requestLogger) + } catch (error) { + return error + } + throw new Error("expected the orphan query to fail") + })() + + expect(queryError).toBe(sqliteError) + expect(requestLogger.warn).not.toHaveBeenCalled() + }) + it("excludes connected notes", () => { const orphans = index.findOrphans({}, logger) const orphanPaths = orphans.map((orphan) => orphan.path) diff --git a/src/vault-mcp/search/search-queries.ts b/src/vault-mcp/search/search-queries.ts index 72af0832b..10bf3f3b8 100644 --- a/src/vault-mcp/search/search-queries.ts +++ b/src/vault-mcp/search/search-queries.ts @@ -1506,7 +1506,25 @@ export const findOrphans = ( LIMIT ? ` - const rows = context.db.prepare(sql).all(...escapedExcludeFolders, limit) + const rows = (() => { + try { + return context.db.prepare(sql).all(...escapedExcludeFolders, limit) + } catch (error) { + const isExclusionCapacityError = + error instanceof Error && + "code" in error && + error.code === "SQLITE_ERROR" && + error.message.startsWith("Expression tree is too large (maximum depth ") + + if (!isExclusionCapacityError) throw error + + logger.warn("orphan exclusion query capacity exceeded", { + excludedFolderCount: excludeFolders.length, + error: describeError(error), + }) + throw new Error("too many excluded folders", { cause: error }) + } + })() const results = rows.map(rowToMetadata) logger.info("find orphans", { count: results.length }) return results From 08e93506d685928addae64383251e590880f9871 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:49:30 -0400 Subject: [PATCH 13/16] fix: normalize daily folder protection and describe orphan defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: pr-monitor · gpt-6.1-sol --- .../__snapshots__/tool-surface/default.json | 4 +-- .../tool-surface/disabled-tools.json | 4 +-- .../tool-surface/embedding-off.json | 4 +-- .../file-tools-off+embedding-off.json | 4 +-- .../tool-surface/file-tools-off.json | 4 +-- .../memory-off+embedding-off.json | 4 +-- ...mory-off+file-tools-off+embedding-off.json | 4 +-- .../memory-off+file-tools-off.json | 4 +-- .../tool-surface/memory-off.json | 4 +-- .../tool-surface/obsidian-sync.json | 4 +-- .../tool-surface/readonly+embedding-off.json | 4 +-- ...readonly+file-tools-off+embedding-off.json | 4 +-- .../tool-surface/readonly+file-tools-off.json | 4 +-- .../readonly+memory-off+embedding-off.json | 4 +-- ...mory-off+file-tools-off+embedding-off.json | 4 +-- .../readonly+memory-off+file-tools-off.json | 4 +-- .../tool-surface/readonly+memory-off.json | 4 +-- .../__snapshots__/tool-surface/readonly.json | 4 +-- .../__tests__/tool-definitions.test.ts | 20 +++++++++----- src/vault-mcp/mcp-core/tools/search-tools.ts | 11 +++++--- .../__tests__/vault-folder-config.test.ts | 27 ++++++++++++++++++- .../vault-operations/vault-folder-config.ts | 2 +- 22 files changed, 83 insertions(+), 49 deletions(-) diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json index 0c798e94f..8cd93102a 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/default.json @@ -272,12 +272,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json index 8ddab0c97..6d3c20a63 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/disabled-tools.json @@ -274,12 +274,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json index 7719edc65..17579c6cd 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/embedding-off.json @@ -274,12 +274,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json index 718f831ae..9317246d0 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off+embedding-off.json @@ -275,12 +275,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json index 9e65e91a0..8a435c5af 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/file-tools-off.json @@ -274,12 +274,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json index 1e0546066..b276cc08a 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+embedding-off.json @@ -229,12 +229,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json index 3844360ca..54a721e39 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off+embedding-off.json @@ -230,12 +230,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json index 0f07be657..b1a757e78 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off+file-tools-off.json @@ -229,12 +229,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json index 33481e159..9e0c3d696 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/memory-off.json @@ -228,12 +228,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json index 553d9f437..381c32a7f 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/obsidian-sync.json @@ -274,12 +274,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json index 7d360fe88..fa9c5b0f3 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+embedding-off.json @@ -9,12 +9,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json index 6f4e0e81f..047c3c63c 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off+embedding-off.json @@ -10,12 +10,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json index 7795dfdbf..c2967c72c 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+file-tools-off.json @@ -9,12 +9,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json index 64305dcc0..e72c88c50 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+embedding-off.json @@ -10,12 +10,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json index 949a3850a..e9ec2915b 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off+embedding-off.json @@ -11,12 +11,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json index b018ee25c..e306cf9a8 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off+file-tools-off.json @@ -10,12 +10,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json index da9ed740c..9cec5fe8a 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly+memory-off.json @@ -9,12 +9,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json index 3d3c62744..6636547d3 100644 --- a/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json +++ b/src/vault-mcp/mcp-core/__tests__/__snapshots__/tool-surface/readonly.json @@ -8,12 +8,12 @@ { "name": "vault_find_orphans", "title": "Find Orphans", - "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"), \"Templates\", and \"About Me\", resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"Expression tree is too large\" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", + "description": "Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).\n\nExample: vault_find_orphans({})\nExample: vault_find_orphans({ exclude_folders: [\"Archive\"], limit: 10 })\n\nWhen to use: Vault maintenance — surfacing notes to integrate into the graph.\nPrefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.\n\nParameters:\n- The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → \"Daily Notes\"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses \"Daily Notes\".\n- exclude_folders replaces the defaults (including an environment override), it does not add to them — include the defaults yourself to keep them. Pass [] for no exclusions. Each entry names a whole folder, subfolders included (\"Projects\" also excludes \"Projects/Archive\" but not \"ProjectsOld/\"), ignoring ASCII letter case.\n- limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.\n\nErrors:\n- An empty array means no orphans were found (after exclusions), not an error.\n- \"too many excluded folders\" — pass a shorter exclude_folders list, then retry.\n\nReturns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.", "inputSchema": { "type": "object", "properties": { "exclude_folders": { - "description": "Folder paths to exclude (e.g. Projects)", + "description": "Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")", "type": "array", "items": { "type": "string", diff --git a/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts b/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts index 2c0134f0c..9dada6b3a 100644 --- a/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts +++ b/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts @@ -489,10 +489,13 @@ describe("config interpolation in descriptions", () => { expect(config.description).toContain("use vault_delete_memory for memory entries") }) - it("vault_find_orphans description references configured exclusion folders", () => { + it("vault_find_orphans schema references configured exclusion folders", () => { const [, config] = requireCustomCall(TOOL_NAMES.VAULT_FIND_ORPHANS) - expect(config.description).toContain(CUSTOM_MEMORY_DIR) - expect(config.description).not.toContain("About Me") + const exclusionDescription = config.inputSchema?.exclude_folders?.description + + expect(exclusionDescription).toBe( + 'Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, "Profile")', + ) }) }) @@ -1616,14 +1619,14 @@ describe("vault_find_orphans live folder defaults", () => { expect(await queryPaths()).toEqual(["Daily Notes/daily.md", "ordinary.md"]) }) - it("describes live default sources and keeps the schema to parameter meaning", async () => { + it("describes live sources and states the exclusion default in the schema", async () => { const { toolConfig } = await setupOrphans({ settings: '{"folder":"Journal"}' }) expect(toolConfig.description).toContain( - 'the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → "Daily Notes"), "Templates", and "About Me"', + 'The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → "Daily Notes")', ) expect(toolConfig.description).not.toContain("Journal") expect(toolConfig.inputSchema?.exclude_folders?.description).toBe( - "Folder paths to exclude (e.g. Projects)", + 'Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, "About Me")', ) }) @@ -1635,7 +1638,10 @@ describe("vault_find_orphans live folder defaults", () => { ?.split("\n") .find((line) => line.startsWith("- With exclude_folders")) expect(defaultsLine).toBe( - '- With exclude_folders omitted, the ORPHAN_EXCLUDE_FOLDERS override excludes ["Archive","Scratch"].', + "- With exclude_folders omitted, the ORPHAN_EXCLUDE_FOLDERS override is used.", + ) + expect(toolConfig.inputSchema?.exclude_folders?.description).toBe( + 'Folder paths to exclude (e.g. Projects; default: ["Archive","Scratch"])', ) }) }) diff --git a/src/vault-mcp/mcp-core/tools/search-tools.ts b/src/vault-mcp/mcp-core/tools/search-tools.ts index 6e1ff7d6c..6cc87cab6 100644 --- a/src/vault-mcp/mcp-core/tools/search-tools.ts +++ b/src/vault-mcp/mcp-core/tools/search-tools.ts @@ -611,9 +611,12 @@ Errors: }, ) + const orphanDefaultFolders = config.orphanExcludeFoldersOverride + ? JSON.stringify(config.orphanExcludeFoldersOverride) + : `daily notes folder, Templates, ${JSON.stringify(config.memoryDir)}` const orphanDefaultDescription = config.orphanExcludeFoldersOverride - ? `With exclude_folders omitted, the ORPHAN_EXCLUDE_FOLDERS override excludes ${JSON.stringify(config.orphanExcludeFoldersOverride)}.` - : `With exclude_folders omitted, defaults are the daily notes folder (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → "Daily Notes"), "Templates", and ${JSON.stringify(config.memoryDir)}, resolved on each call. ORPHAN_EXCLUDE_FOLDERS replaces that list. For unreadable daily settings, the server logs a warning and uses "Daily Notes".` + ? "With exclude_folders omitted, the ORPHAN_EXCLUDE_FOLDERS override is used." + : 'The daily notes folder is resolved on each call (DAILY_NOTES_FOLDER → .obsidian/daily-notes.json → "Daily Notes"). ORPHAN_EXCLUDE_FOLDERS replaces the defaults. For unreadable daily settings, the server logs a warning and uses "Daily Notes".' registerTool( TOOL_NAMES.VAULT_FIND_ORPHANS, @@ -634,14 +637,14 @@ Parameters: Errors: - An empty array means no orphans were found (after exclusions), not an error. -- "Expression tree is too large" — the exclusion list exceeds the query's capacity; pass a shorter exclude_folders list, then retry. +- "too many excluded folders" — pass a shorter exclude_folders list, then retry. Returns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.`, inputSchema: { exclude_folders: z .array(z.string().min(1)) .optional() - .describe("Folder paths to exclude (e.g. Projects)"), + .describe(`Folder paths to exclude (e.g. Projects; default: ${orphanDefaultFolders})`), limit: z.number().int().min(1).optional().default(50).describe("Max results (default 50)"), }, }, diff --git a/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts b/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts index 18575a946..8889d9335 100644 --- a/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts +++ b/src/vault-mcp/vault-operations/__tests__/vault-folder-config.test.ts @@ -1,9 +1,10 @@ import { describe, expect, it, onTestFinished, vi } from "vitest" -import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises" +import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises" import { tmpdir } from "node:os" import { join } from "node:path" import { loadConfig } from "../../config.js" import { logger } from "../../../logger.js" +import { vaultFs } from "../vault-filesystem.js" import { readEffectiveOrphanExcludeFolders, resolveEffectiveOrphanExcludeFolders, @@ -64,6 +65,30 @@ describe("resolveEffectiveProtectedPaths", () => { ).toEqual(["About Me", " Journal "]) }) + it("refuses deletion under a file-configured folder with repeated trailing separators", async () => { + const vaultPath = await createVault('{"folder":" Journal ///"}') + await mkdir(join(vaultPath, " Journal ")) + await writeFile(join(vaultPath, " Journal /daily.md"), "daily body\n") + const protectedPaths = await resolveEffectiveProtectedPaths( + { config: loadConfig({}), vaultPath }, + logger, + ) + + await expect( + vaultFs.deleteNote( + { + vaultPath, + path: " Journal /daily.md", + protectedPaths, + pruneEmptyFolders: false, + trashOption: "local", + }, + logger, + ), + ).rejects.toThrow(new Error('cannot delete protected path " Journal /daily.md"')) + expect(await readFile(join(vaultPath, " Journal /daily.md"), "utf8")).toBe("daily body\n") + }) + it("uses DAILY_NOTES_FOLDER without reading the file when the format is unset", async () => { const vaultPath = await createVault("broken") const requestLogger = { ...logger, warn: vi.fn() } diff --git a/src/vault-mcp/vault-operations/vault-folder-config.ts b/src/vault-mcp/vault-operations/vault-folder-config.ts index e9db788df..6aa86e4c8 100644 --- a/src/vault-mcp/vault-operations/vault-folder-config.ts +++ b/src/vault-mcp/vault-operations/vault-folder-config.ts @@ -24,7 +24,7 @@ export const resolveEffectiveProtectedPaths = async ( const dailyNotesConfig = await readDailyNotesFileConfig(vaultPath, logger) // A whitespace-only folder in daily-notes.json protects nothing. - const dailyFolder = dailyNotesConfig.folder + const dailyFolder = dailyNotesConfig.folder.replace(/\/+$/, "") return dailyFolder.trim() ? [config.memoryDir, dailyFolder] : [config.memoryDir] } From 2feb7468a7b55b8584c1f6d40dc9d405fc2d03ec Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:05:39 -0400 Subject: [PATCH 14/16] fix: normalize daily forward-reference prefixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: pr-monitor · gpt-6.1-sol --- .devin/wiki.json | 6 +- .../search/__tests__/search-index.test.ts | 87 ++++++++++++------- src/vault-mcp/search/search-queries.ts | 6 +- 3 files changed, 61 insertions(+), 38 deletions(-) diff --git a/.devin/wiki.json b/.devin/wiki.json index 9fe45e5e0..95975131b 100644 --- a/.devin/wiki.json +++ b/.devin/wiki.json @@ -4,7 +4,7 @@ "content": "Vault Cortex is a remote MCP (Model Context Protocol) server that exposes an Obsidian vault over HTTPS — vault CRUD, hybrid search, task management, structured memory, link-graph queries, and non-markdown file reading (images, PDFs, canvases, data files), plus user-initiated prompts. It runs headless in Docker, working directly with .md files on disk using its own Obsidian-parity parsers and search index. README.md is the authoritative feature description and ARCHITECTURE.md the authoritative design document — derive capability details, defaults, and security specifics from those and the code rather than restating them. The repo also ships a CLI (cli/, published as the vault-cortex npm package) that scaffolds a deployment: npx vault-cortex@latest init." }, { - "content": "The codebase is organized into four internal layers under src/vault-mcp/: (1) obsidian-markdown/ — pure Obsidian/Markdown parsers with no I/O (links, lines/fences, frontmatter, callouts, headings, tasks, memory entries, canvas, plaintext). (2) vault-operations/ — filesystem I/O for vault content (vault-filesystem, vault-patcher, note-mover, memory-store, daily-notes, task-mutations, task-format-config, trash-sweeper, asset-operations). (3) search/ — the SQLite FTS5 + sqlite-vec index, embedding pipeline, and chokidar file watcher. (4) mcp-core/ — the MCP protocol surface: streamable-http transport, tool group modules under mcp-core/tools/, and prompt group modules under mcp-core/prompts/. Authentication lives in src/vault-mcp/oauth/ (OAuth 2.1) and src/functions/authorizer.ts (AWS Lambda, path-aware). The :remote image's setup mode — the browser sign-in served before an Obsidian Sync token exists — lives in src/vault-mcp/setup/. Infrastructure-as-code (sst.config.ts) is specific to the AWS reference deployment and should not be documented as the primary deployment path." + "content": "The codebase is organized into four internal layers under src/vault-mcp/: (1) obsidian-markdown/ — pure Obsidian/Markdown parsers with no I/O (links, lines/fences, frontmatter, callouts, headings, tasks, memory entries, canvas, plaintext). (2) vault-operations/ — filesystem I/O for vault content (vault-filesystem, vault-patcher, note-mover, memory-store, daily-notes, vault-folder-config, task-mutations, task-format-config, trash-sweeper, asset-operations). (3) search/ — the SQLite FTS5 + sqlite-vec index, embedding pipeline, and chokidar file watcher. (4) mcp-core/ — the MCP protocol surface: streamable-http transport, tool group modules under mcp-core/tools/, and prompt group modules under mcp-core/prompts/. Authentication lives in src/vault-mcp/oauth/ (OAuth 2.1) and src/functions/authorizer.ts (AWS Lambda, path-aware). The :remote image's setup mode — the browser sign-in served before an Obsidian Sync token exists — lives in src/vault-mcp/setup/. Infrastructure-as-code (sst.config.ts) is specific to the AWS reference deployment and should not be documented as the primary deployment path." } ], "pages": [ @@ -22,7 +22,7 @@ }, { "title": "Configuration Reference", - "purpose": "All environment variables as defined in config.ts and the .env.example files, the ServerConfig type, and how configuration differs across the deployment modes. Enumerate the variables from the source at index time." + "purpose": "All environment variables as defined in config.ts and the .env.example files, the VaultConfig type, and how configuration differs across the deployment modes. Enumerate the variables from the source at index time." }, { "title": "Architecture", @@ -45,7 +45,7 @@ }, { "title": "Vault Operations", - "purpose": "The filesystem I/O layer in vault-operations/: vault-filesystem.ts (base primitives including atomic writes and path safety), vault-patcher.ts, note-mover.ts, memory-store.ts, daily-notes.ts, task-mutations.ts, task-format-config.ts, trash-sweeper.ts (boot-time orphan-row purge + daily retention sweep over recorded .trash/ entries), and asset-operations.ts (the use-case behind vault_read_file and vault_list_files). Derive the safety mechanisms (locking modes, TOCTOU guards, protected paths) from ARCHITECTURE.md's Data integrity section.", + "purpose": "The filesystem I/O layer in vault-operations/: vault-filesystem.ts (base primitives including atomic writes and path safety), vault-patcher.ts, note-mover.ts, memory-store.ts, daily-notes.ts, vault-folder-config.ts (protected folders and orphan exclusion defaults), task-mutations.ts, task-format-config.ts, trash-sweeper.ts (boot-time orphan-row purge + daily retention sweep over recorded .trash/ entries), and asset-operations.ts (the use-case behind vault_read_file and vault_list_files). Derive the safety mechanisms (locking modes, TOCTOU guards, protected paths) from ARCHITECTURE.md's Data integrity section.", "parent": "Architecture" }, { diff --git a/src/vault-mcp/search/__tests__/search-index.test.ts b/src/vault-mcp/search/__tests__/search-index.test.ts index ea9d9fbe8..740b379c2 100644 --- a/src/vault-mcp/search/__tests__/search-index.test.ts +++ b/src/vault-mcp/search/__tests__/search-index.test.ts @@ -3688,28 +3688,42 @@ describe("getOutgoingLinks", () => { expect(broken?.bytes).toBeNull() }) - it("flags daily note forward-refs when the folder is passed", () => { - index.upsertNote( - { - filePath: "Daily Notes/2026-06-24.md", - rawContent: "# 2026-06-24\n\n[[Daily Notes/2026-06-25|Tomorrow >>]] and [[missing]].\n", - fileStat: testStat(1000), - }, - logger, - ) - - const links = index.getOutgoingLinks( - { path: "Daily Notes/2026-06-24.md", dailyNotesFolder: "Daily Notes" }, - logger, - ) - const forwardRef = links.find((link) => link.path === "Daily Notes/2026-06-25") - expect(forwardRef?.exists).toBe(false) - expect(forwardRef?.daily_note_forward_ref).toBe(true) + it.each(["Daily Notes", "Daily Notes/", "Daily Notes///"])( + "flags daily note forward-refs with folder %s", + (dailyNotesFolder) => { + index.upsertNote( + { + filePath: "Daily Notes/2026-06-24.md", + rawContent: "# 2026-06-24\n\n[[Daily Notes/2026-06-25|Tomorrow >>]] and [[missing]].\n", + fileStat: testStat(1000), + }, + logger, + ) - const genuinelyBroken = links.find((link) => link.path === "missing") - expect(genuinelyBroken?.exists).toBe(false) - expect(genuinelyBroken?.daily_note_forward_ref).toBe(false) - }) + const links = index.getOutgoingLinks( + { path: "Daily Notes/2026-06-24.md", dailyNotesFolder }, + logger, + ) + expect(links).toEqual([ + { + path: "Daily Notes/2026-06-25", + title: null, + exists: false, + kind: "note", + bytes: null, + daily_note_forward_ref: true, + }, + { + path: "missing", + title: null, + exists: false, + kind: "note", + bytes: null, + daily_note_forward_ref: false, + }, + ]) + }, + ) it("returns empty for notes with no outgoing links", () => { index.upsertNote( @@ -4339,18 +4353,25 @@ describe("brokenLinkCount", () => { expect(outgoing[0]?.kind).toBe("note") }) - it("excludes forward-reference links that are valid dates under the daily note folder", () => { - index.upsertNote( - { - filePath: "Daily Notes/2026-06-24.md", - rawContent: - "# 2026-06-24\n\n[[Daily Notes/2026-06-25|Tomorrow >>]] and [[missing-note]].\n", - fileStat: testStat(1000), - }, - logger, - ) - expect(index.brokenLinkCount({ dailyNotesFolder: "Daily Notes" }, logger).count).toBe(1) - }) + it.each(["Daily Notes", "Daily Notes/", "Daily Notes///"])( + "excludes daily forward references with folder %s and retains sibling-folder failures", + (dailyNotesFolder) => { + index.upsertNote( + { + filePath: "Daily Notes/2026-06-24.md", + rawContent: + "# 2026-06-24\n\n[[Daily Notes/2026-06-25|Tomorrow >>]] and [[missing-note]] and [[Daily Notes Extra/missing]].\n", + fileStat: testStat(1000), + }, + logger, + ) + expect(index.brokenLinkCount({ dailyNotesFolder }, logger)).toEqual({ + count: 2, + excludedFolder: dailyNotesFolder, + excludedCount: 1, + }) + }, + ) it("excludes .md-suffixed forward-reference targets", () => { index.upsertNote( diff --git a/src/vault-mcp/search/search-queries.ts b/src/vault-mcp/search/search-queries.ts index 10bf3f3b8..364c536d3 100644 --- a/src/vault-mcp/search/search-queries.ts +++ b/src/vault-mcp/search/search-queries.ts @@ -1458,7 +1458,9 @@ export const getOutgoingLinks = ( } >(sql) .all(params.path) - const dailyNotesFolderPrefix = params.dailyNotesFolder ? `${params.dailyNotesFolder}/` : null + const dailyNotesFolderPrefix = params.dailyNotesFolder + ? `${stripTrailingSlashes(params.dailyNotesFolder)}/` + : null const results: OutgoingLinkEntry[] = rows.map((row) => ({ path: row.path, title: row.title, @@ -1568,7 +1570,7 @@ export const brokenLinkCount = ( return { count, excludedFolder: null, excludedCount: 0 } } - const excludedFolderPrefix = `${excludedFolder}/` + const excludedFolderPrefix = `${stripTrailingSlashes(excludedFolder)}/` const brokenTargets = context.db .prepare( `SELECT DISTINCT target From 534ae89c5241a1203c272b9213791bb2082f52b5 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:40:10 -0400 Subject: [PATCH 15/16] fix: clarify local settings reads and daily folder labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: pr-monitor · gpt-6.1-sol --- README.md | 2 +- cli/src/env.ts | 5 +++-- deploy/local/.env.example | 5 +++-- .../__tests__/vault-orientation-prompt.test.ts | 15 +++++++++++++++ .../mcp-core/prompts/vault-orientation-prompt.ts | 4 +++- 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dc9aef240..594b9a752 100644 --- a/README.md +++ b/README.md @@ -420,7 +420,7 @@ When the file isn't available — or if you use the Periodic Notes plugin, whose You can set one or both — a set value always wins over the config file. Without either source, the server falls back to `Daily Notes` and `YYYY-MM-DD`. -- Changes in Obsidian apply on the next call once `.obsidian/daily-notes.json` reaches the server. Apply environment changes with the CLI's `restart` command, recreate the Compose container, or redeploy on your hosting platform. +- Changes in Obsidian apply on the next call when `.obsidian/daily-notes.json` is available in the server's vault. Apply environment changes with the CLI's `restart` command, recreate the Compose container, or redeploy on your hosting platform. - An empty Obsidian folder setting uses the server's `Daily Notes` fallback. Daily notes stored at the vault root remain eligible for orphan results; the server never excludes the whole vault automatically. > **Note:** A few date format tokens are unsupported — ordinals (`Do`, `Mo`, `DDDo`, `wo`), `dd` (2-letter weekday), `d` (weekday number), `e`, `k`/`kk`, `w` (week number), `Q` (quarter), `Z`/`ZZ` (UTC offset), and the localized formats (`L`–`LLLL`, `LT`, `LTS`). The server can't reproduce the filenames Obsidian creates with these tokens, so it could never find the notes. If your format uses any of them, `vault_get_daily_note` returns a clear error — change the format in Obsidian or set `DAILY_NOTES_FORMAT` to a supported alternative. diff --git a/cli/src/env.ts b/cli/src/env.ts index d847c1049..1276653ac 100644 --- a/cli/src/env.ts +++ b/cli/src/env.ts @@ -112,8 +112,9 @@ MEMORY_DIR=About Me # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. # Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. -# Apply env changes with CLI restart or recreate the Compose container; -# Obsidian settings apply on the next query once daily-notes.json reaches the server. +# Apply env changes with CLI restart or recreate the Compose container. +# Obsidian settings apply on the next query; daily-notes.json is read +# directly from your bind-mounted vault. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata diff --git a/deploy/local/.env.example b/deploy/local/.env.example index 98457c130..554825dce 100644 --- a/deploy/local/.env.example +++ b/deploy/local/.env.example @@ -102,8 +102,9 @@ MEMORY_DIR=About Me # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. # Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. -# Apply env changes with CLI restart or recreate the Compose container; -# Obsidian settings apply on the next query once daily-notes.json reaches the server. +# Apply env changes with CLI restart or recreate the Compose container. +# Obsidian settings apply on the next query; daily-notes.json is read +# directly from your bind-mounted vault. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me # URL shown in OAuth discovery metadata diff --git a/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts b/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts index e2798c2ea..505627d92 100644 --- a/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts +++ b/src/vault-mcp/mcp-core/prompts/__tests__/vault-orientation-prompt.test.ts @@ -93,6 +93,21 @@ describe("vault-orientation live orphan folders", () => { ) }) + it.each(["Journal/", "Journal///"])( + "renders one trailing separator for the excluded daily folder %s", + async (dailyNotesFolder) => { + const { readSurveySections } = await setupOrphanPrompt({ + settings: JSON.stringify({ folder: dailyNotesFolder }), + paths: ["source.md", "other.md"], + noteContents: { "source.md": dailyForwardReferences }, + }) + + expect((await readSurveySections()).stats).toBe( + "2 notes across 0 folders, 0 tags, 0 property keys. 2 untagged. 2 without properties. 2 broken links (excludes 2 forward-refs in Journal/).", + ) + }, + ) + it("keeps ordinary orphans visible behind more than five newer excluded daily candidates", async () => { const dailyPaths = Array.from({ length: 7 }, (_, index) => `Journal/nested/day-${index}.md`) const { orphanSection } = await setupOrphanPrompt({ diff --git a/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts b/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts index 932b5e7ca..ea03ef905 100644 --- a/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts +++ b/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts @@ -105,9 +105,11 @@ const formatBrokenLinkSegment = (result: { excludedCount: number }): string => { const { count, excludedFolder, excludedCount } = result + const excludedFolderLabel = `${excludedFolder}/`.replace(/\/+$/, "/") + const forwardRefLabel = excludedCount === 1 ? "forward-ref" : "forward-refs" const excludedNote = excludedCount > 0 - ? `excludes ${excludedCount} forward-ref${excludedCount === 1 ? "" : "s"} in ${excludedFolder}/` + ? `excludes ${excludedCount} ${forwardRefLabel} in ${excludedFolderLabel}` : "" if (count === 0 && excludedNote.length === 0) return "" From 603811915b35fa393b3e7b17b46e0afa640c65e4 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:46:32 -0400 Subject: [PATCH 16/16] style: clarify restart guidance and forward reference label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship-Check: code-quality · gpt-6.1-sol --- cli/src/env.ts | 6 ++++-- deploy/local/.env.example | 3 ++- deploy/remote/.env.example | 3 ++- src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cli/src/env.ts b/cli/src/env.ts index 1276653ac..c40cdea0c 100644 --- a/cli/src/env.ts +++ b/cli/src/env.ts @@ -112,7 +112,8 @@ MEMORY_DIR=About Me # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. # Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. -# Apply env changes with CLI restart or recreate the Compose container. +# To apply environment changes, run the CLI's restart command +# or recreate the Compose container. # Obsidian settings apply on the next query; daily-notes.json is read # directly from your bind-mounted vault. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me @@ -260,7 +261,8 @@ MEMORY_DIR=About Me # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. # Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. -# Apply env changes with CLI restart or recreate the Compose container; +# To apply environment changes, run the CLI's restart command +# or recreate the Compose container. # Obsidian settings apply on the next query once daily-notes.json reaches the server. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me diff --git a/deploy/local/.env.example b/deploy/local/.env.example index 554825dce..ebcb35390 100644 --- a/deploy/local/.env.example +++ b/deploy/local/.env.example @@ -102,7 +102,8 @@ MEMORY_DIR=About Me # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. # Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. -# Apply env changes with CLI restart or recreate the Compose container. +# To apply environment changes, run the CLI's restart command +# or recreate the Compose container. # Obsidian settings apply on the next query; daily-notes.json is read # directly from your bind-mounted vault. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me diff --git a/deploy/remote/.env.example b/deploy/remote/.env.example index 2f459acb2..04fe00eda 100644 --- a/deploy/remote/.env.example +++ b/deploy/remote/.env.example @@ -109,7 +109,8 @@ MEMORY_DIR=About Me # on each query, falling back to "Daily Notes". Root-level daily notes remain eligible. # When set, replaces the whole default — include every folder to keep excluded. # Set ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing; an empty value uses the defaults. -# Apply env changes with CLI restart or recreate the Compose container; +# To apply environment changes, run the CLI's restart command +# or recreate the Compose container. # Obsidian settings apply on the next query once daily-notes.json reaches the server. # ORPHAN_EXCLUDE_FOLDERS=Daily Notes,Templates,About Me diff --git a/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts b/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts index ea03ef905..5939338be 100644 --- a/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts +++ b/src/vault-mcp/mcp-core/prompts/vault-orientation-prompt.ts @@ -106,10 +106,10 @@ const formatBrokenLinkSegment = (result: { }): string => { const { count, excludedFolder, excludedCount } = result const excludedFolderLabel = `${excludedFolder}/`.replace(/\/+$/, "/") - const forwardRefLabel = excludedCount === 1 ? "forward-ref" : "forward-refs" + const forwardReferenceLabel = excludedCount === 1 ? "forward-ref" : "forward-refs" const excludedNote = excludedCount > 0 - ? `excludes ${excludedCount} ${forwardRefLabel} in ${excludedFolderLabel}` + ? `excludes ${excludedCount} ${forwardReferenceLabel} in ${excludedFolderLabel}` : "" if (count === 0 && excludedNote.length === 0) return ""