From fb51f22fb73f0f41b2877dfab54bf29f490c645c Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:12:30 -0400 Subject: [PATCH 1/9] fix: read the last property of a note with Windows line endings without a carriage return gray-matter closes the properties block at `\n---`, so in a note with Windows line endings the block kept the `\r` of its last line. The YAML parser read it into the last value (`title: "C\r"`), which every rewrite then saved, or refused the whole block when that value was quoted or a bracketed list. splitPropertiesBlock now drops that `\r`, so the block reads as the same note with LF endings does. Note bodies keep their bytes. Co-Authored-By: Claude Opus 5.5 --- .../__tests__/frontmatter.test.ts | 50 +++++++++++++++---- .../obsidian-markdown/frontmatter.ts | 13 +++-- .../__tests__/memory-store.test.ts | 31 ++++++++++++ 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/src/vault-mcp/obsidian-markdown/__tests__/frontmatter.test.ts b/src/vault-mcp/obsidian-markdown/__tests__/frontmatter.test.ts index ddb8c66dd..38d5c7447 100644 --- a/src/vault-mcp/obsidian-markdown/__tests__/frontmatter.test.ts +++ b/src/vault-mcp/obsidian-markdown/__tests__/frontmatter.test.ts @@ -139,13 +139,19 @@ describe("parseNote", () => { }) }) - it("parses CRLF frontmatter", () => { - // The \r inside the value is gray-matter's pre-existing CRLF - // handling, pinned as-is — the opener gate must not reject the file - expect(parseNote("---\r\ntitle: x\r\n---\r\nbody\r\n")).toEqual({ - data: { title: "x\r" }, - content: "body\r\n", - }) + it.each([ + { label: "a plain value", block: "title: C", data: { title: "C" } }, + { label: "a quoted value", block: 'title: "C"', data: { title: "C" } }, + { label: "a flow list", block: "tags: [a, b]", data: { tags: ["a", "b"] } }, + { label: "a block list", block: "tags:\r\n - a\r\n - b", data: { tags: ["a", "b"] } }, + { label: "an empty value", block: "due:", data: { due: null } }, + { + label: "the second of two properties", + block: "title: C\r\ntype: x", + data: { title: "C", type: "x" }, + }, + ])("reads $label last in a CRLF block as the LF block reads it", ({ block, data }) => { + expect(parseNote(`---\r\n${block}\r\n---\r\nbody\r\n`)).toEqual({ data, content: "body\r\n" }) }) it("strips a BOM before an ordinary frontmatter block", () => { @@ -198,6 +204,11 @@ describe("parseNote", () => { note: '---\ntitle: "abc\n---\nbody\n', message: 'properties block is not valid YAML at line 2, column 12: Missing closing "quote', }, + { + label: "an unbalanced quote on the last line of a CRLF block", + note: '---\r\ntitle: "abc\r\n---\r\nbody\r\n', + message: 'properties block is not valid YAML at line 2, column 12: Missing closing "quote', + }, { label: "an alias with no anchor, which has no position", note: "---\nsummary: *bold*\n---\nbody\n", @@ -399,6 +410,13 @@ describe("parseNoteForRewrite", () => { ])("accepts $label", ({ note, data }) => { expect(parseNoteForRewrite(note)).toEqual({ data, content: "body\n" }) }) + + it("accepts an empty CRLF block as no properties", () => { + expect(parseNoteForRewrite("---\r\n---\r\nbody\r\n")).toEqual({ + data: {}, + content: "body\r\n", + }) + }) }) // ── splitPropertiesBlock ───────────────────────────────────────── @@ -448,9 +466,15 @@ describe("splitPropertiesBlock", () => { body: "---text\ntitle: x\n---\nbody\n", }, { - label: "CRLF line endings", + label: "CRLF line endings, without the carriage return before the closer", note: "---\r\ntitle: x\r\n---\r\nbody\r\n", - blockText: "\r\ntitle: x\r", + blockText: "\r\ntitle: x", + body: "body\r\n", + }, + { + label: "an empty CRLF block", + note: "---\r\n---\r\nbody\r\n", + blockText: "", body: "body\r\n", }, { @@ -617,6 +641,14 @@ describe("stringifyNote", () => { it.each([ { label: "a valid block", body: "---\ntitle: ok\n---\nbody\n" }, { label: "a CRLF block", body: "---\r\ntitle: ok\r\n---\r\nbody\r\n" }, + { + label: "a CRLF block ending in a quoted value", + body: '---\r\ntitle: "ok"\r\n---\r\nbody\r\n', + }, + { + label: "a CRLF block ending in a list of quoted links", + body: '---\r\ntitle: ok\r\nrelated:\r\n - "[[Note]]"\r\n---\r\nbody\r\n', + }, { label: "a Multi Column opener", body: "--- start-multi-column: X\ntext\n" }, { label: "a horizontal rule with no closer", body: "---\nno closer here\n" }, ])("writes an empty-data body that opens with $label verbatim", ({ body }) => { diff --git a/src/vault-mcp/obsidian-markdown/frontmatter.ts b/src/vault-mcp/obsidian-markdown/frontmatter.ts index a3ffa37f8..2f30f1632 100644 --- a/src/vault-mcp/obsidian-markdown/frontmatter.ts +++ b/src/vault-mcp/obsidian-markdown/frontmatter.ts @@ -75,7 +75,7 @@ const SPLIT_ONLY_OPTIONS = { * line is a bare `---` AND a later line starts with `---` — the rule * Obsidian's metadata cache follows. Otherwise `blockText` is null and the * body is the whole note. A leading BOM is stripped on both paths, as - * gray-matter does. + * gray-matter does. The body keeps its bytes, line endings included. * * `tasks.findBodyStartLine` finds where the body starts by its own, * stricter rule: a first line and a later line that are exactly `---`. @@ -84,7 +84,8 @@ export const splitPropertiesBlock = ( content: string, ): { /** The text between the `---` lines. It starts with the opener line's own - * newline, so its line numbers match the note's. */ + * newline, so its line numbers match the note's, and ends without the + * line break before the closer, as in an LF note. */ blockText: string | null body: string } => { @@ -92,7 +93,13 @@ export const splitPropertiesBlock = ( if (hasFrontmatterFences) { const file = matter(content, SPLIT_ONLY_OPTIONS) - return { blockText: file.matter, body: file.content } + + // gray-matter closes the block at `\n---`, so in a note with Windows line + // endings the `\r` of the last line's break stays at the end of the + // block. The YAML parser would read it as part of the last value + // (`title: "C\r"`), or refuse the block when that value is quoted. + const blockText = file.matter.endsWith("\r") ? file.matter.slice(0, -1) : file.matter + return { blockText, body: file.content } } const contentWithoutBom = content.startsWith("\uFEFF") ? content.slice(1) : content return { blockText: null, body: contentWithoutBom } diff --git a/src/vault-mcp/vault-operations/__tests__/memory-store.test.ts b/src/vault-mcp/vault-operations/__tests__/memory-store.test.ts index b275738b4..a896a21d1 100644 --- a/src/vault-mcp/vault-operations/__tests__/memory-store.test.ts +++ b/src/vault-mcp/vault-operations/__tests__/memory-store.test.ts @@ -1992,6 +1992,37 @@ describe("listMemoryFiles", () => { expect(living?.entry_policy).toBe("living") }) + it("reads entry-policy living as the last property of a file with Windows line endings", async () => { + const content = [ + "---", + "title: Living", + "entry-policy: living", + "---", + "", + "# Living", + "", + "## Upcoming (newest first)", + "- **2026-07-11**: a current-state entry", + "", + ].join("\r\n") + await writeFile(join(vault, "About Me/Living.md"), content, "utf8") + + const outlines = await listMemoryFiles({ vaultPath: vault }, logger) + const living = outlines.find((outline) => outline.file === "Living") + + expect(living).toEqual({ + file: "Living", + title: "Living", + bytes: Buffer.byteLength(content, "utf8"), + entry_policy: "living", + leading_callout: null, + headings: [ + { level: 1, text: "Living" }, + { level: 2, text: "Upcoming (newest first)", entry_count: 1 }, + ], + }) + }) + it("treats an unrecognized entry-policy value as append-only", async () => { // Only the explicit "living" opt-in relaxes append-only; a typo must not // silently authorize destructive maintenance. From c86cf4f2be1cb200ebdc5067c7243e89328c715f Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:15:09 -0400 Subject: [PATCH 2/9] fix: match and write Windows line breaks in edit-tool text as the note's LF The edit tools match against a note body split into LF lines, so old_text copied from a raw read of a CRLF note never matched, and \r\n in new_text or content was written byte for byte into LF output. replace_in_note now converts old_text and new_text, and patch, replace_span and insert_at_anchor split content with splitIntoLines, which also lets the leading-heading check drop its own carriage-return strip. Co-Authored-By: Claude Opus 5.5 --- .../__tests__/vault-patcher.test.ts | 178 ++++++++++++++++++ .../vault-operations/vault-patcher.ts | 42 ++--- 2 files changed, 199 insertions(+), 21 deletions(-) diff --git a/src/vault-mcp/vault-operations/__tests__/vault-patcher.test.ts b/src/vault-mcp/vault-operations/__tests__/vault-patcher.test.ts index 30061e101..f9e4ff2af 100644 --- a/src/vault-mcp/vault-operations/__tests__/vault-patcher.test.ts +++ b/src/vault-mcp/vault-operations/__tests__/vault-patcher.test.ts @@ -4950,6 +4950,184 @@ duplicate beta }) }) +describe("Windows line breaks (CRLF)", () => { + const CRLF_NOTE = "---\r\ntitle: C\r\n---\r\n# H\r\nA\r\n- [ ] task\r\nB\r\n" + const LF_NOTE = "---\ntitle: C\n---\n# H\nA\n- [ ] task\nB\n" + const NOTES = [ + { label: "an LF note", note: LF_NOTE }, + { label: "a CRLF note", note: CRLF_NOTE }, + ] + const CRLF_CONTENT = "X1\r\nX2" + + describe("replaceInNote", () => { + it("matches old_text copied with CRLF line breaks", async () => { + await writeTestNote("crlf.md", CRLF_NOTE) + const result = await replaceInNote( + { + vaultPath: vault, + path: "crlf.md", + oldText: "- [ ] task\r\n", + newText: "- [x] task\r\n", + }, + logger, + ) + + expect(result).toEqual({ message: "Replaced 1 occurrence in crlf.md", count: 1 }) + expect(await readTestNote("crlf.md")).toBe("---\ntitle: C\n---\n# H\nA\n- [x] task\nB\n") + }) + + it("converts new_text's CRLF line breaks to LF", async () => { + await writeTestNote("lf.md", LF_NOTE) + await replaceInNote( + { vaultPath: vault, path: "lf.md", oldText: "task", newText: "one\r\ntwo" }, + logger, + ) + + expect(await readTestNote("lf.md")).toBe("---\ntitle: C\n---\n# H\nA\n- [ ] one\ntwo\nB\n") + }) + + it("removes every CRLF match as the LF old_text would, keeping each match's wider gap", async () => { + // The wider gap sits below the first match, so measuring the gaps with + // the unconverted old_text, which is longer, would change the output. + await writeTestNote( + "gaps.md", + "---\r\ntitle: Gaps\r\n---\r\nA\r\n\r\nX\r\nY\r\n\r\n\r\n\r\nB\r\n\r\nX\r\nY\r\n\r\nC\r\n", + ) + const result = await replaceInNote( + { + vaultPath: vault, + path: "gaps.md", + oldText: "X\r\nY\r\n", + newText: "", + replaceAllOccurrences: true, + }, + logger, + ) + + expect(result).toEqual({ message: "Replaced 2 occurrences in gaps.md", count: 2 }) + expect(await readTestNote("gaps.md")).toBe("---\ntitle: Gaps\n---\nA\n\n\n\nB\n\nC\n") + }) + + it("still finds no match for a lone carriage return", async () => { + await writeTestNote("crlf.md", CRLF_NOTE) + + await expect( + replaceInNote({ vaultPath: vault, path: "crlf.md", oldText: "\r", newText: "" }, logger), + ).rejects.toThrow(new Error('text not found in "crlf.md": "\r"')) + expect(await readTestNote("crlf.md")).toBe(CRLF_NOTE) + }) + }) + + describe("content with CRLF line breaks", () => { + it.each(NOTES)("appends it to $label with LF line breaks", async ({ note }) => { + await writeTestNote("note.md", note) + await patchNote( + { vaultPath: vault, path: "note.md", operation: "append", content: CRLF_CONTENT }, + logger, + ) + + expect(await readTestNote("note.md")).toBe( + "---\ntitle: C\n---\n# H\nA\n- [ ] task\nB\n\nX1\nX2\n", + ) + }) + + it.each(NOTES)( + "prepends it under a heading of $label with LF line breaks", + async ({ note }) => { + await writeTestNote("note.md", note) + await patchNote( + { + vaultPath: vault, + path: "note.md", + operation: "prepend", + heading: "H", + content: CRLF_CONTENT, + }, + logger, + ) + + expect(await readTestNote("note.md")).toBe( + "---\ntitle: C\n---\n# H\nX1\nX2\nA\n- [ ] task\nB\n", + ) + }, + ) + + it.each(NOTES)("replaces a span of $label with it, using LF line breaks", async ({ note }) => { + await writeTestNote("note.md", note) + await replaceSpan( + { vaultPath: vault, path: "note.md", startAnchor: "task", content: CRLF_CONTENT }, + logger, + ) + + expect(await readTestNote("note.md")).toBe("---\ntitle: C\n---\n# H\nA\nX1\nX2\nB\n") + }) + + it.each(NOTES)("inserts it into $label with LF line breaks", async ({ note }) => { + await writeTestNote("note.md", note) + await insertAtAnchor( + { + vaultPath: vault, + path: "note.md", + anchor: "task", + position: "after", + content: CRLF_CONTENT, + }, + logger, + ) + + expect(await readTestNote("note.md")).toBe( + "---\ntitle: C\n---\n# H\nA\n- [ ] task\nX1\nX2\nB\n", + ) + }) + + it("counts the blank CRLF lines at the edges of replacement content as empty lines", async () => { + await writeTestNote("gaps.md", "---\ntitle: Gaps\n---\n\nA\n\nX\n\nB\n") + await replaceSpan( + { vaultPath: vault, path: "gaps.md", startAnchor: "X", content: "\r\n\r\nY\r\n\r\n" }, + logger, + ) + + expect(await readTestNote("gaps.md")).toBe("---\ntitle: Gaps\n---\n\nA\n\n\nY\n\n\nB\n") + }) + + it("still refuses content that repeats the target heading with a CRLF line break", async () => { + await writeTestNote("note.md", "---\r\ntitle: C\r\n---\r\n## H\r\nA\r\n") + + await expect( + patchNote( + { + vaultPath: vault, + path: "note.md", + operation: "append", + heading: "H", + content: "## H\r\nX", + }, + logger, + ), + ).rejects.toThrow( + new Error( + 'content begins with the heading "## H", which would duplicate it — heading-targeted ops keep the matched heading, so omit the heading line from content.', + ), + ) + }) + }) + + it("edits a CRLF note whose last property is a list of quoted links, keeping its properties", async () => { + await writeTestNote( + "related.md", + '---\r\ntitle: "C"\r\nrelated:\r\n - "[[Note]]"\r\n---\r\nA\r\n', + ) + await patchNote( + { vaultPath: vault, path: "related.md", operation: "append", content: "Z" }, + logger, + ) + + expect(await readTestNote("related.md")).toBe( + '---\ntitle: C\nrelated:\n - "[[Note]]"\n---\nA\n\nZ\n', + ) + }) +}) + describe("hidden paths", () => { // The note exists on disk so a removed guard would make the operations // succeed — the tests then fail because the edit went through, never via diff --git a/src/vault-mcp/vault-operations/vault-patcher.ts b/src/vault-mcp/vault-operations/vault-patcher.ts index d14b9ed3a..f0fa1557c 100644 --- a/src/vault-mcp/vault-operations/vault-patcher.ts +++ b/src/vault-mcp/vault-operations/vault-patcher.ts @@ -56,19 +56,11 @@ type PatchNoteResult = Readonly<{ /** The heading a content block begins with — the heading on its first non-blank * line — or null when it starts with anything else. parseHeadings makes this - * fence-aware, so a `## foo` inside an opening code fence is not a heading. - * - * Strips a trailing CR before parsing: HEADING_REGEX uses `(.*)` where `.` - * excludes CR, so a CRLF-authored `## New\r` would otherwise read as ordinary - * text and report nothing. Detection only — callers insert the caller's own - * lines verbatim, line endings untouched. */ + * fence-aware, so a `## foo` inside an opening code fence is not a heading. */ const leadingHeadingOfContent = (contentLines: readonly string[]): HeadingInfo | null => { - const normalizedLines = contentLines.map((line) => { - return line.endsWith("\r") ? line.slice(0, -1) : line - }) - const firstContentLineIndex = normalizedLines.findIndex((line) => line.trim() !== "") + const firstContentLineIndex = contentLines.findIndex((line) => line.trim() !== "") return ( - parseHeadings(normalizedLines).find( + parseHeadings(contentLines).find( (contentHeading) => contentHeading.startLine === firstContentLineIndex, ) ?? null ) @@ -405,7 +397,7 @@ const patchNote = async ( const lockPath = resolveSafePath(params.vaultPath, path) return withExclusiveFileLock(lockPath, async () => { const { fullPath, data, lines, beforeBytes } = await readNoteForPatch(params.vaultPath, path) - const contentLines = content.split("\n") + const contentLines = splitIntoLines(content) // File-level operation (no heading target) if (!heading) { @@ -517,31 +509,39 @@ const replaceInNote = async ( } assertNoControlCharacters(newText, "new_text") + // The body is matched with LF line breaks (readNoteForPatch splits it with + // splitIntoLines), so the caller's Windows line breaks are converted too: + // - text copied from a raw read of a CRLF note then matches; + // - replaceAll, unlike splitIntoLines, keeps a lone `\r` at the end, so + // old_text "\r" never becomes an empty search text matching the note's top. + const searchText = oldText.replaceAll("\r\n", "\n") + const replacementText = newText.replaceAll("\r\n", "\n") + const lockPath = resolveSafePath(params.vaultPath, path) return withExclusiveFileLock(lockPath, async () => { const { fullPath, data, lines, beforeBytes } = await readNoteForPatch(params.vaultPath, path) const body = lines.join("\n") - const firstMatchOffset = body.indexOf(oldText) + const firstMatchOffset = body.indexOf(searchText) if (firstMatchOffset === -1) { throw new Error(`text not found in "${path}": "${truncateForMessage(oldText)}"`) } const bodyParts = replaceAllOccurrences - ? body.split(oldText) - : [body.slice(0, firstMatchOffset), body.slice(firstMatchOffset + oldText.length)] + ? body.split(searchText) + : [body.slice(0, firstMatchOffset), body.slice(firstMatchOffset + searchText.length)] const count = bodyParts.length - 1 - const updatedLines = bodyParts.join(newText).split("\n") + const updatedLines = bodyParts.join(replacementText).split("\n") // A deletion stacks the empty lines from both sides of each match into one // run, so that run is shrunk back. A replacement keeps new text between - // them, and its line breaks are the caller's, written exactly as given. - const isDeletion = newText.length === 0 + // them, and its line breaks are written where the caller put them. + const isDeletion = replacementText.length === 0 const writtenLines = isDeletion ? collapseEmptyLineRunsAtEdits({ lines: updatedLines, - edits: listTextDeletionEdits({ body, bodyParts, removedText: oldText }), + edits: listTextDeletionEdits({ body, bodyParts, removedText: searchText }), }) : updatedLines @@ -664,7 +664,7 @@ const replaceSpan = async ( }) const replacedLineCount = endLine - startLine + 1 - const contentLines = content.split("\n") + const contentLines = splitIntoLines(content) const updatedLines = lines.toSpliced(startLine, replacedLineCount, ...contentLines) // Content is a side of both edits: its leading empty lines meet the note's @@ -739,7 +739,7 @@ const insertAtAnchor = async ( path, }) - const contentLines = content.split("\n") + const contentLines = splitIntoLines(content) const insertIndex = position === "before" ? anchorLine : anchorLine + 1 const updatedLines = lines.toSpliced(insertIndex, 0, ...contentLines) From 1d61ec44982c29e99cc90bf1c8d9062bbd7dd42a Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:15:48 -0400 Subject: [PATCH 3/9] docs(tools): say that vault_read_note heading sections come back with LF line endings The description said unpaged reads stay byte-identical, but an unpaged heading read of a note with Windows line endings returns LF lines. It now limits the byte-for-byte promise to unpaged full reads, and drops "verbatim" from the heading-mode pointer so the two statements agree. Co-Authored-By: Claude Opus 5.5 --- .../__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 +- .../__tests__/__snapshots__/tool-surface/readonly.json | 2 +- src/vault-mcp/mcp-core/tools/vault-crud-tools.ts | 4 ++-- 19 files changed, 20 insertions(+), 20 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 5272c524c..b944eabec 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 @@ -1094,7 +1094,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 6b28ce559..6c87099e6 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 @@ -1036,7 +1036,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 ee3201a0c..1d8ab3a87 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 @@ -1096,7 +1096,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 f31a4543d..a4f745051 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 @@ -1012,7 +1012,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 2665324fd..c09933af9 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 @@ -1011,7 +1011,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 2add073b7..da9593a0f 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 @@ -957,7 +957,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 20dde2b19..471953198 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 @@ -873,7 +873,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 bfce05e7a..a1ba3021b 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 @@ -872,7 +872,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 0835a604a..51c4b465f 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 @@ -956,7 +956,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 287872d34..7232fbeb6 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 @@ -1096,7 +1096,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 021662a9b..3758c3048 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 @@ -679,7 +679,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 0d9cb764f..77b46b6f9 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 @@ -595,7 +595,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 f02053ecc..74b81e285 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 @@ -594,7 +594,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 bf0c4a5a3..6f97f0782 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 @@ -586,7 +586,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 1061601c6..a3961b401 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 @@ -502,7 +502,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 c643e7c34..1e751cbbb 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 @@ -501,7 +501,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 e09a26b91..c042a7eb0 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 @@ -585,7 +585,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 ac82ad67e..572dbfae7 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 @@ -678,7 +678,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "inputSchema": { "type": "object", "properties": { 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 1c4b130ba..ba9aebaf2 100644 --- a/src/vault-mcp/mcp-core/tools/vault-crud-tools.ts +++ b/src/vault-mcp/mcp-core/tools/vault-crud-tools.ts @@ -81,9 +81,9 @@ Example: vault_read_note({ path: "TASKS.md", heading: "Done", heading_level: 2 } Example: vault_read_note({ path: "TASKS.md", heading: "Done", start_line: 1, limit: 20 }) // first 20 lines of an oversized section When to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: "..." to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total. -Prefer vault_search when you don't know the path.${whenToolEnabledText("vault_list_tasks", " For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown.")}${whenToolEnabledText("vault_get_memory", ` Prefer vault_get_memory for ${config.memoryDir}/ files (returns content without properties).`)}${whenToolEnabledText("vault_patch_note", " To edit a section you've read, use vault_patch_note.")} To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links. +Prefer vault_search when you don't know the path.${whenToolEnabledText("vault_list_tasks", " For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown.")}${whenToolEnabledText("vault_get_memory", ` Prefer vault_get_memory for ${config.memoryDir}/ files (returns content without properties).`)}${whenToolEnabledText("vault_patch_note", " To edit a section you've read, use vault_patch_note.")} To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links. -Section boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Paged reads normalize line endings to LF; unpaged reads stay byte-identical. +Section boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings. Errors: - "note not found" — no note exists at this path; verify it with vault_list_notes From c0e841e2e535a7e3c194ab154dd1164e1ba10a7c Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:18:20 -0400 Subject: [PATCH 4/9] test: pin the reworded vault_read_note board pointer Co-Authored-By: Claude Opus 5.5 --- src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 589070b7f..1db767a7a 100644 --- a/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts +++ b/src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts @@ -2560,7 +2560,7 @@ describe("DISABLED_TOOLS", () => { " Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links." expect(readNoteRoutingLine("")).toBe( - `${ROUTING_LINE_START} For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's verbatim Markdown.${ROUTING_LINE_END}`, + `${ROUTING_LINE_START} For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown.${ROUTING_LINE_END}`, ) expect(readNoteRoutingLine("vault_list_tasks")).toBe(`${ROUTING_LINE_START}${ROUTING_LINE_END}`) }) From 06132d12036ea2ea2bbf9877a450836641f0ba74 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:32:43 -0400 Subject: [PATCH 5/9] style: explain CRLF handling in the properties split and the edit tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - frontmatter.ts: say why only the last line's carriage return is dropped, what the block text keeps, why line numbers match the note, and that replacing a CRLF note's block leaves mixed line endings. - vault-patcher.ts: comment each content split's CRLF conversion, the old_text conversion and its empty-string guard, the anchor and insert exceptions, and the LF-only offsets in listTextDeletionEdits; one spelling for empty-string guards; firstNonBlankLineIndex. - vault_read_note: name what full reads match, move the line-ending sentence to Returns, and say "Kanban board". Snapshot regenerated. Ship-Check: code-quality · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 --- .../__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 +- .../mcp-core/tools/vault-crud-tools.ts | 6 +-- .../obsidian-markdown/frontmatter.ts | 32 ++++++++---- .../vault-operations/vault-patcher.ts | 49 +++++++++++++------ 21 files changed, 77 insertions(+), 46 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 b944eabec..f55484ba0 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 @@ -1094,7 +1094,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 6c87099e6..e9bb7e56d 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 @@ -1036,7 +1036,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 1d8ab3a87..28a5ece9e 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 @@ -1096,7 +1096,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 a4f745051..c60b31349 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 @@ -1012,7 +1012,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 c09933af9..ddb45068f 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 @@ -1011,7 +1011,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 da9593a0f..9244afb97 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 @@ -957,7 +957,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 471953198..dbad6e3c3 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 @@ -873,7 +873,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 a1ba3021b..e705cad91 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 @@ -872,7 +872,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 51c4b465f..3c21a93c4 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 @@ -956,7 +956,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 7232fbeb6..c31936e1f 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 @@ -1096,7 +1096,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 3758c3048..c2cc16c70 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 @@ -679,7 +679,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 77b46b6f9..b16a5257d 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 @@ -595,7 +595,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 74b81e285..b03cc8d30 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 @@ -594,7 +594,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 6f97f0782..512f85619 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 @@ -586,7 +586,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 a3961b401..e0621715c 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 @@ -502,7 +502,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 1e751cbbb..0f2c3294e 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 @@ -501,7 +501,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 c042a7eb0..6362248a6 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 @@ -585,7 +585,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 572dbfae7..4704c0b7f 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 @@ -678,7 +678,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\").\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "inputSchema": { "type": "object", "properties": { 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 ba9aebaf2..75745fcae 100644 --- a/src/vault-mcp/mcp-core/tools/vault-crud-tools.ts +++ b/src/vault-mcp/mcp-core/tools/vault-crud-tools.ts @@ -80,10 +80,10 @@ Example: vault_read_note({ path: "TASKS.md", heading: "Active" }) Example: vault_read_note({ path: "TASKS.md", heading: "Done", heading_level: 2 }) // disambiguate when several "Done" headings exist Example: vault_read_note({ path: "TASKS.md", heading: "Done", start_line: 1, limit: 20 }) // first 20 lines of an oversized section -When to use: You know the exact path and need a specific note's content. For a large note (a long board or doc), use outline: true to see its headings and any text sitting above them, then heading: "..." to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total. +When to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: "..." to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total. Prefer vault_search when you don't know the path.${whenToolEnabledText("vault_list_tasks", " For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown.")}${whenToolEnabledText("vault_get_memory", ` Prefer vault_get_memory for ${config.memoryDir}/ files (returns content without properties).`)}${whenToolEnabledText("vault_patch_note", " To edit a section you've read, use vault_patch_note.")} To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links. -Section boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Unpaged full reads are byte-identical; heading sections and paged reads use LF line endings. +Section boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. Errors: - "note not found" — no note exists at this path; verify it with vault_list_notes @@ -98,7 +98,7 @@ Errors: - "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it - "properties block is not valid YAML …" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it -Returns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block ("path — lines 1–20 of 250 (continue with start_line: 21)"). +Returns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block ("path — lines 1–20 of 250 (continue with start_line: 21)"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF. Outline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings ("##" with no text) appear with text: "" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note${whenToolEnabledText("vault_replace_in_note", ", and edit via vault_replace_in_note")}.`, inputSchema: { diff --git a/src/vault-mcp/obsidian-markdown/frontmatter.ts b/src/vault-mcp/obsidian-markdown/frontmatter.ts index 2f30f1632..f898aa969 100644 --- a/src/vault-mcp/obsidian-markdown/frontmatter.ts +++ b/src/vault-mcp/obsidian-markdown/frontmatter.ts @@ -78,14 +78,16 @@ const SPLIT_ONLY_OPTIONS = { * gray-matter does. The body keeps its bytes, line endings included. * * `tasks.findBodyStartLine` finds where the body starts by its own, - * stricter rule: a first line and a later line that are exactly `---`. + * stricter rule: a first line and a later line that are exactly `---`. It + * reads lines from `splitIntoLines`, so a CRLF note's `---\r` counts as `---`. */ export const splitPropertiesBlock = ( content: string, ): { /** The text between the `---` lines. It starts with the opener line's own - * newline, so its line numbers match the note's, and ends without the - * line break before the closer, as in an LF note. */ + * line break, so its line numbers match the note's. Its lines keep the + * note's line breaks, `\r\n` included; only the `\r` before the closer is + * dropped, so the last line reads as in an LF note. */ blockText: string | null body: string } => { @@ -94,13 +96,20 @@ export const splitPropertiesBlock = ( if (hasFrontmatterFences) { const file = matter(content, SPLIT_ONLY_OPTIONS) - // gray-matter closes the block at `\n---`, so in a note with Windows line - // endings the `\r` of the last line's break stays at the end of the - // block. The YAML parser would read it as part of the last value - // (`title: "C\r"`), or refuse the block when that value is quoted. + // gray-matter closes the block at `\n---`, which splits the last line's + // `\r\n` break in two and leaves its `\r` at the end of the block: + // - earlier lines need nothing, because the YAML parser reads `\r\n` as a + // line break; + // - this `\r` has no `\n` after it, so the parser would read it as part of + // the last value (`title: "C\r"`), or refuse the block when that value + // is quoted. + // A `\r` here is always half of that break, whatever line endings the + // rest of the note uses, so it is dropped in every note. const blockText = file.matter.endsWith("\r") ? file.matter.slice(0, -1) : file.matter return { blockText, body: file.content } } + + // gray-matter strips the BOM on the path above; this path does it here const contentWithoutBom = content.startsWith("\uFEFF") ? content.slice(1) : content return { blockText: null, body: contentWithoutBom } } @@ -210,8 +219,9 @@ const convertDocumentToValue = ( */ const readPropertiesBlock = (blockText: string): PropertiesBlockReading => { // Each call gets its own counter, because a shared one keeps counting - // across parses and reports later errors on the wrong line. The block - // text starts with the opener's own newline, so its lines are the note's. + // across parses and reports later errors on the wrong line. The block text + // from splitPropertiesBlock starts with the opener's own line break and + // keeps the note's line breaks, so line N of the block is line N of the note. const lineCounter = new LineCounter() // prettyErrors would append its own position and a multi-line excerpt of // the block to each message; the message below states the position itself @@ -363,7 +373,9 @@ export const stringifyNote = (body: string, data: object): string => { * Replaces a note's whole properties block with `properties` and keeps the * body's bytes exactly, adding no newline; only a leading BOM is dropped, * as on every split. The old block is never parsed, so a block the server - * cannot read or keep can still be replaced. `{}` removes the block. + * cannot read or keep can still be replaced. `{}` removes the block. The new + * block always uses LF, so a CRLF note comes back with mixed line endings, + * as Obsidian's own property edits leave it. */ export const replacePropertiesBlock = (content: string, properties: object): string => { const { body } = splitPropertiesBlock(content) diff --git a/src/vault-mcp/vault-operations/vault-patcher.ts b/src/vault-mcp/vault-operations/vault-patcher.ts index f0fa1557c..7dcd4beb1 100644 --- a/src/vault-mcp/vault-operations/vault-patcher.ts +++ b/src/vault-mcp/vault-operations/vault-patcher.ts @@ -55,13 +55,14 @@ type PatchNoteResult = Readonly<{ // ── Internal helpers ──────────────────────────────────────────── /** The heading a content block begins with — the heading on its first non-blank - * line — or null when it starts with anything else. parseHeadings makes this - * fence-aware, so a `## foo` inside an opening code fence is not a heading. */ + * line (blank: empty or whitespace only) — or null when that line is not a + * heading or every line is blank. parseHeadings makes this fence-aware, so a + * `## foo` inside an opening code fence is not a heading. */ const leadingHeadingOfContent = (contentLines: readonly string[]): HeadingInfo | null => { - const firstContentLineIndex = contentLines.findIndex((line) => line.trim() !== "") + const firstNonBlankLineIndex = contentLines.findIndex((line) => line.trim() !== "") return ( parseHeadings(contentLines).find( - (contentHeading) => contentHeading.startLine === firstContentLineIndex, + (contentHeading) => contentHeading.startLine === firstNonBlankLineIndex, ) ?? null ) } @@ -224,6 +225,8 @@ type LineBreakRun = Readonly<{ start: number; end: number }> /** Edit points for a `replaceInNote` deletion. `bodyParts` is the body split at * every removed match, so `bodyParts.join(removedText)` is the original body. + * - Every line break is a single `\n`: the body comes from splitIntoLines, and + * replaceInNote converts the caller's `\r\n` before matching. * - Only a match whose removal leaves an empty line where it sat gets an edit * point; any other removal is written exactly as asked. * - Each gap counts the empty lines between the match's text and the nearest @@ -270,6 +273,8 @@ const listTextDeletionEdits = (params: { updatedBodyOffset += partBeforeMatch.length updatedLineIndex += partBeforeMatch.split("\n").length - 1 + // The removal leaves an empty line when a line break or the body's edge + // sits on both sides of the point where the match was. const startsLine = updatedBodyOffset === 0 || updatedBody[updatedBodyOffset - 1] === "\n" const endsLine = updatedBodyOffset === updatedBody.length || updatedBody[updatedBodyOffset] === "\n" @@ -321,6 +326,8 @@ const resolveAnchorLine = (params: { }): number => { const { lines, anchor, fromLine, firstMatch, path, role } = params const anchorLabel = role ? `${role} anchor` : "anchor" + // Anchors get no line-break conversion, unlike old_text: an anchor is part + // of one line, so a line break in it would match no line either way. const matchingLineIndices = lines.flatMap((line, index) => { return index >= fromLine && line.includes(anchor) ? [index] : [] }) @@ -397,6 +404,8 @@ const patchNote = async ( const lockPath = resolveSafePath(params.vaultPath, path) return withExclusiveFileLock(lockPath, async () => { const { fullPath, data, lines, beforeBytes } = await readNoteForPatch(params.vaultPath, path) + // The control-character check allows `\r`, so content may hold `\r\n` + // breaks; splitIntoLines turns them into the LF lines the body uses. const contentLines = splitIntoLines(content) // File-level operation (no heading target) @@ -459,6 +468,8 @@ const patchNote = async ( // Replace on a section with child headings requires explicit opt-in — // without it, the caller may not realize children will be destroyed. if (operation === "replace" && !includeChildren) { + // A section's body runs to the next heading of the same or higher level, + // so every heading inside that range is a child of the target. const childHeadings = headings.filter((candidate) => { return ( candidate.startLine >= target.bodyStartLine && candidate.startLine < target.bodyEndLine @@ -504,16 +515,17 @@ const replaceInNote = async ( ): Promise<{ message: string; count: number }> => { const { path, oldText, newText, replaceAllOccurrences } = params - if (oldText.length === 0) { + if (oldText === "") { throw new Error("oldText cannot be empty") } + // Only new_text is written to the note; old_text is only matched against it assertNoControlCharacters(newText, "new_text") // The body is matched with LF line breaks (readNoteForPatch splits it with - // splitIntoLines), so the caller's Windows line breaks are converted too: + // splitIntoLines), so the caller's `\r\n` breaks become `\n` too: // - text copied from a raw read of a CRLF note then matches; - // - replaceAll, unlike splitIntoLines, keeps a lone `\r` at the end, so - // old_text "\r" never becomes an empty search text matching the note's top. + // - replaceAll never empties a non-empty string, unlike splitIntoLines, which + // would turn old_text "\r" into "", a search text that matches at offset 0. const searchText = oldText.replaceAll("\r\n", "\n") const replacementText = newText.replaceAll("\r\n", "\n") @@ -524,6 +536,7 @@ const replaceInNote = async ( const body = lines.join("\n") const firstMatchOffset = body.indexOf(searchText) + // The message quotes the text the caller sent, before line-break conversion if (firstMatchOffset === -1) { throw new Error(`text not found in "${path}": "${truncateForMessage(oldText)}"`) } @@ -537,7 +550,7 @@ const replaceInNote = async ( // A deletion stacks the empty lines from both sides of each match into one // run, so that run is shrunk back. A replacement keeps new text between // them, and its line breaks are written where the caller put them. - const isDeletion = replacementText.length === 0 + const isDeletion = replacementText === "" const writtenLines = isDeletion ? collapseEmptyLineRunsAtEdits({ lines: updatedLines, @@ -574,7 +587,7 @@ const deleteSpan = async ( ): Promise => { const { path, startAnchor, endAnchor, firstMatch } = params - if (startAnchor.length === 0) { + if (startAnchor === "") { throw new Error("startAnchor cannot be empty") } if (endAnchor === "") { @@ -640,13 +653,13 @@ const replaceSpan = async ( ): Promise => { const { path, startAnchor, endAnchor, content, firstMatch } = params - if (startAnchor.length === 0) { + if (startAnchor === "") { throw new Error("startAnchor cannot be empty") } if (endAnchor === "") { throw new Error("endAnchor cannot be empty") } - if (content.length === 0) { + if (content === "") { throw new Error("content cannot be empty") } assertNoControlCharacters(content, "content") @@ -664,13 +677,15 @@ const replaceSpan = async ( }) const replacedLineCount = endLine - startLine + 1 + // splitIntoLines turns content's `\r\n` breaks into the LF lines the body uses const contentLines = splitIntoLines(content) const updatedLines = lines.toSpliced(startLine, replacedLineCount, ...contentLines) // Content is a side of both edits: its leading empty lines meet the note's // empty lines above the old span, and its trailing ones meet those below. // Each joined run keeps the wider side's count, and the content's interior - // is written as given. + // is written as given. Both boundaries index updatedLines before any run + // is collapsed. const writtenLines = collapseEmptyLineRunsAtEdits({ lines: updatedLines, edits: [ @@ -719,10 +734,10 @@ const insertAtAnchor = async ( ): Promise => { const { path, anchor, position, content, firstMatch } = params - if (anchor.length === 0) { + if (anchor === "") { throw new Error("anchor cannot be empty") } - if (content.length === 0) { + if (content === "") { throw new Error("content cannot be empty") } assertNoControlCharacters(content, "content") @@ -739,10 +754,14 @@ const insertAtAnchor = async ( path, }) + // splitIntoLines turns content's `\r\n` breaks into the LF lines the body uses const contentLines = splitIntoLines(content) const insertIndex = position === "before" ? anchorLine : anchorLine + 1 const updatedLines = lines.toSpliced(insertIndex, 0, ...contentLines) + // No empty-line collapse, unlike the span edits: no text is removed, so no + // two runs of the note's empty lines end up touching, and the content's + // own empty lines are written as given. const afterBytes = await writePatchedNote(fullPath, data, updatedLines, logger) logger.info("inserted at anchor", { From 702cf566312c1f0fecdd02a9d49a1d846be63001 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:33:58 -0400 Subject: [PATCH 6/9] docs: drop an inexact Obsidian comparison from replacePropertiesBlock's docstring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Obsidian's Properties panel rewrites the whole note with LF; only its processFrontMatter API leaves mixed line endings. Ship-Check: triage · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 --- src/vault-mcp/obsidian-markdown/frontmatter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vault-mcp/obsidian-markdown/frontmatter.ts b/src/vault-mcp/obsidian-markdown/frontmatter.ts index f898aa969..739313ff0 100644 --- a/src/vault-mcp/obsidian-markdown/frontmatter.ts +++ b/src/vault-mcp/obsidian-markdown/frontmatter.ts @@ -374,8 +374,7 @@ export const stringifyNote = (body: string, data: object): string => { * body's bytes exactly, adding no newline; only a leading BOM is dropped, * as on every split. The old block is never parsed, so a block the server * cannot read or keep can still be replaced. `{}` removes the block. The new - * block always uses LF, so a CRLF note comes back with mixed line endings, - * as Obsidian's own property edits leave it. + * block always uses LF, so a CRLF note comes back with mixed line endings. */ export const replacePropertiesBlock = (content: string, properties: object): string => { const { body } = splitPropertiesBlock(content) From 9b1feb0fa7d63b8fdaaf7fe503df762d0443ef80 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:40:02 -0400 Subject: [PATCH 7/9] test: pin CRLF handling in update_properties, section reads and the not-found quote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - updateProperties on a CRLF note keeps the last property without a carriage return and the body's CRLF bytes - readNoteSection returns a CRLF note's section with LF line breaks, as the vault_read_note description now states - replaceInNote's not-found message quotes old_text as sent, CRLF included - the CRLF old_text test sends an LF new_text, so it fails only when old_text matching breaks Ship-Check: test-audit · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 --- .../__tests__/vault-filesystem.test.ts | 23 +++++++++++++++++++ .../__tests__/vault-patcher.test.ts | 14 ++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/vault-mcp/vault-operations/__tests__/vault-filesystem.test.ts b/src/vault-mcp/vault-operations/__tests__/vault-filesystem.test.ts index 1077520dd..1b9976eca 100644 --- a/src/vault-mcp/vault-operations/__tests__/vault-filesystem.test.ts +++ b/src/vault-mcp/vault-operations/__tests__/vault-filesystem.test.ts @@ -2276,6 +2276,18 @@ describe("updateProperties", () => { expect(written).toBe("---\ntitle: Original\nstatus: active\n---\n---\nbody after rule\n") }) + it("keeps a CRLF note's last property without a carriage return and its body's bytes", async () => { + await writeFile(join(vault, "crlf.md"), "---\r\ntitle: C\r\ntype: x\r\n---\r\nbody\r\n", "utf8") + await updateProperties( + { vaultPath: vault, path: "crlf.md", properties: { status: "active" } }, + logger, + ) + + // The new block uses LF; the body keeps its CRLF line breaks + const written = await readFile(join(vault, "crlf.md"), "utf8") + expect(written).toBe("---\ntitle: C\ntype: x\nstatus: active\n---\nbody\r\n") + }) + it("merges new keys without changing body", async () => { await writeFile(join(vault, "test.md"), "---\ntitle: Original\n---\nBody content\n", "utf8") await updateProperties( @@ -3239,6 +3251,17 @@ describe("readNoteSection", () => { ) expect(section).toBe("## Plan\nStep one\n") }) + + it("returns a section of a CRLF note with LF line breaks", async () => { + const content = "---\r\ntitle: C\r\n---\r\n## Plan\r\nStep one\r\n\r\n## Later\r\nStep two\r\n" + await writeFile(join(vault, "crlf.md"), content, "utf8") + + const section = await readNoteSection( + { vaultPath: vault, path: "crlf.md", heading: "Plan" }, + logger, + ) + expect(section).toBe("## Plan\nStep one\n") + }) }) // ── Concurrent writes ───────────────────────────────────────── diff --git a/src/vault-mcp/vault-operations/__tests__/vault-patcher.test.ts b/src/vault-mcp/vault-operations/__tests__/vault-patcher.test.ts index f9e4ff2af..18f5ac8ca 100644 --- a/src/vault-mcp/vault-operations/__tests__/vault-patcher.test.ts +++ b/src/vault-mcp/vault-operations/__tests__/vault-patcher.test.ts @@ -4967,7 +4967,7 @@ describe("Windows line breaks (CRLF)", () => { vaultPath: vault, path: "crlf.md", oldText: "- [ ] task\r\n", - newText: "- [x] task\r\n", + newText: "- [x] task\n", }, logger, ) @@ -5008,6 +5008,18 @@ describe("Windows line breaks (CRLF)", () => { expect(await readTestNote("gaps.md")).toBe("---\ntitle: Gaps\n---\nA\n\n\n\nB\n\nC\n") }) + it("quotes old_text as sent, CRLF line breaks included, when it matches nothing", async () => { + await writeTestNote("crlf.md", CRLF_NOTE) + + await expect( + replaceInNote( + { vaultPath: vault, path: "crlf.md", oldText: "missing\r\n", newText: "" }, + logger, + ), + ).rejects.toThrow(new Error('text not found in "crlf.md": "missing\r\n"')) + expect(await readTestNote("crlf.md")).toBe(CRLF_NOTE) + }) + it("still finds no match for a lone carriage return", async () => { await writeTestNote("crlf.md", CRLF_NOTE) From f9c1cfd3a22c01f4d6379d507e7ce0e1877caf22 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:48:26 -0400 Subject: [PATCH 8/9] docs: say the properties block text starts with the end of the opener line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The opener may carry trailing spaces or tabs, which stay at the start of the block text, so the text does not always start with the line break. Line numbers still match the note's. Ship-Check: bug-check · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 --- src/vault-mcp/obsidian-markdown/frontmatter.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/vault-mcp/obsidian-markdown/frontmatter.ts b/src/vault-mcp/obsidian-markdown/frontmatter.ts index 739313ff0..05bfda9ff 100644 --- a/src/vault-mcp/obsidian-markdown/frontmatter.ts +++ b/src/vault-mcp/obsidian-markdown/frontmatter.ts @@ -84,10 +84,10 @@ const SPLIT_ONLY_OPTIONS = { export const splitPropertiesBlock = ( content: string, ): { - /** The text between the `---` lines. It starts with the opener line's own - * line break, so its line numbers match the note's. Its lines keep the - * note's line breaks, `\r\n` included; only the `\r` before the closer is - * dropped, so the last line reads as in an LF note. */ + /** The text between the `---` lines. It starts with the end of the opener + * line, line break included, so its line numbers match the note's. Its + * lines keep the note's line breaks, `\r\n` included; only the `\r` before + * the closer is dropped, so the last line reads as in an LF note. */ blockText: string | null body: string } => { @@ -218,10 +218,11 @@ const convertDocumentToValue = ( * that would stringify back as UTC-Z. */ const readPropertiesBlock = (blockText: string): PropertiesBlockReading => { - // Each call gets its own counter, because a shared one keeps counting - // across parses and reports later errors on the wrong line. The block text - // from splitPropertiesBlock starts with the opener's own line break and - // keeps the note's line breaks, so line N of the block is line N of the note. + // - Each call gets its own counter, because a shared one keeps counting + // across parses and reports later errors on the wrong line. + // - The block text from splitPropertiesBlock starts with the end of the + // opener line, line break included, and keeps the note's line breaks, so + // line N of the block is line N of the note. const lineCounter = new LineCounter() // prettyErrors would append its own position and a multi-line excerpt of // the block to each message; the message below states the position itself From f786fdad5588b9549500f19d49d4dc38acbb8e52 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:59:57 -0400 Subject: [PATCH 9/9] docs(tools): list vault_read_note's filesystem errors and its empty properties result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tool-definition review of this branch's vault_read_note edit found gaps the tool already had: - ENAMETOOLONG and EACCES (or another filesystem error code) reach the client, but no Errors entry named them; tool-helpers says each tool lists these when its description is next revised. - properties_only returns {} for a note with no properties. - The one-mode-per-call rule sat under "Section boundaries". - The ".md" entry lost its remedy when vault_read_file is off. The per-tool size allowance rises from 4,226 to 4,234 to fit them. Ship-Check: tool-definitions · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 --- .../__tests__/__snapshots__/tool-surface/default.json | 2 +- .../__snapshots__/tool-surface/disabled-tools.json | 2 +- .../__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 +- .../__snapshots__/tool-surface/memory-off.json | 2 +- .../__snapshots__/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 +- ...donly+memory-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 +- .../mcp-core/__tests__/tool-surface-snapshot.test.ts | 2 +- src/vault-mcp/mcp-core/tools/vault-crud-tools.ts | 11 ++++++----- 20 files changed, 25 insertions(+), 24 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 f55484ba0..2519b62c0 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 @@ -1094,7 +1094,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 e9bb7e56d..29f788b54 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 @@ -1036,7 +1036,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 28a5ece9e..effc9efd8 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 @@ -1096,7 +1096,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 c60b31349..92905468a 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 @@ -1012,7 +1012,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 ddb45068f..d284c4b8c 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 @@ -1011,7 +1011,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 9244afb97..8b639743f 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 @@ -957,7 +957,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 dbad6e3c3..ffac4f220 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 @@ -873,7 +873,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 e705cad91..f11266c8d 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 @@ -872,7 +872,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 3c21a93c4..6e7ef8f03 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 @@ -956,7 +956,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 c31936e1f..746a63c04 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 @@ -1096,7 +1096,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To edit a section you've read, use vault_patch_note. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.", "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 c2cc16c70..9bfacb887 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 @@ -679,7 +679,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 b16a5257d..6e3a5baf8 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 @@ -595,7 +595,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 b03cc8d30..075239886 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 @@ -594,7 +594,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 512f85619..bc7f83bdf 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 @@ -586,7 +586,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 e0621715c..e0fed47f2 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 @@ -502,7 +502,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 0f2c3294e..d6253d90a 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 @@ -501,7 +501,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 6362248a6..c09c73e4e 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 @@ -585,7 +585,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "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 4704c0b7f..dd134ed8c 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 @@ -678,7 +678,7 @@ { "name": "vault_read_note", "title": "Read Note", - "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading.\n\nErrors:\n- \"note not found\" — no note exists at this path; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n\nReturns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", + "description": "Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.\n\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\" })\nExample: vault_read_note({ path: \"Projects/vault-cortex.md\", properties_only: true })\nExample: vault_read_note({ path: \"TASKS.md\", outline: true })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Active\" })\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", heading_level: 2 }) // disambiguate when several \"Done\" headings exist\nExample: vault_read_note({ path: \"TASKS.md\", heading: \"Done\", start_line: 1, limit: 20 }) // first 20 lines of an oversized section\n\nWhen to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: \"...\" to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total.\nPrefer vault_search when you don't know the path. For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown. Prefer vault_get_memory for About Me/ files (returns content without properties). To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links.\n\nSection boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.\n\nErrors:\n- \"note not found\" / \"ENAMETOOLONG: …\" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes\n- \"heading not found\" — no heading matches the text; error lists available headings\n- \"ambiguous heading\" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level\n- \"outline, heading, and properties_only are mutually exclusive\" — only one mode per call\n- \"heading_level requires a heading\" — heading_level only disambiguates a heading; pass heading with it\n- \"heading cannot be empty\" — heading is whitespace only; pass the heading's text\n- \"line paging is not available in outline mode\" / \"... properties_only mode\" — start_line/limit only work on text renditions (full read or heading section)\n- \"start line past the end\" — start_line exceeds the rendition's line count; error states the total\n- 'path must end in \".md\"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file\n- \"absolute path blocked\" / \"path traversal blocked\" / \"hidden path blocked\" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it\n- \"properties block is not valid YAML …\" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it\n- \"EACCES: …\" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it\n\nReturns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block (\"path — lines 1–20 of 250 (continue with start_line: 21)\"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.\n\nOutline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings (\"##\" with no text) appear with text: \"\" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note.", "inputSchema": { "type": "object", "properties": { diff --git a/src/vault-mcp/mcp-core/__tests__/tool-surface-snapshot.test.ts b/src/vault-mcp/mcp-core/__tests__/tool-surface-snapshot.test.ts index 705fe95e2..0b19f6954 100644 --- a/src/vault-mcp/mcp-core/__tests__/tool-surface-snapshot.test.ts +++ b/src/vault-mcp/mcp-core/__tests__/tool-surface-snapshot.test.ts @@ -36,7 +36,7 @@ describe("tool surface baseline", () => { * - The allowance is about 2% above the default combo's average when the * cap was set (4,130 chars per tool), so only small growth passes. * Raising the allowance is a deliberate change with its reason in the PR. */ - const CHARS_PER_TOOL_ALLOWANCE = 4226 + const CHARS_PER_TOOL_ALLOWANCE = 4234 it.each(SIZE_CAPPED_COMBO_NAMES)( "combo %s stays within the average per-tool size allowance", 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 75745fcae..5a50195d2 100644 --- a/src/vault-mcp/mcp-core/tools/vault-crud-tools.ts +++ b/src/vault-mcp/mcp-core/tools/vault-crud-tools.ts @@ -71,7 +71,7 @@ export const registerVaultCrudTools = ({ TOOL_NAMES.VAULT_READ_NOTE, { title: "Read Note", - description: `Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. + description: `Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading. Example: vault_read_note({ path: "Projects/vault-cortex.md" }) Example: vault_read_note({ path: "Projects/vault-cortex.md", properties_only: true }) @@ -83,10 +83,10 @@ Example: vault_read_note({ path: "TASKS.md", heading: "Done", start_line: 1, lim When to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: "..." to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total. Prefer vault_search when you don't know the path.${whenToolEnabledText("vault_list_tasks", " For task status or order on a board, prefer vault_list_tasks; heading mode returns a lane's Markdown.")}${whenToolEnabledText("vault_get_memory", ` Prefer vault_get_memory for ${config.memoryDir}/ files (returns content without properties).`)}${whenToolEnabledText("vault_patch_note", " To edit a section you've read, use vault_patch_note.")} To explore what links to this note or what it links to, use vault_get_backlinks and vault_get_outgoing_links. -Section boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Modes are mutually exclusive — set at most one of properties_only, outline, or heading. +Section boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included. Errors: -- "note not found" — no note exists at this path; verify it with vault_list_notes +- "note not found" / "ENAMETOOLONG: …" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes - "heading not found" — no heading matches the text; error lists available headings - "ambiguous heading" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level - "outline, heading, and properties_only are mutually exclusive" — only one mode per call @@ -94,11 +94,12 @@ Errors: - "heading cannot be empty" — heading is whitespace only; pass the heading's text - "line paging is not available in outline mode" / "... properties_only mode" — start_line/limit only work on text renditions (full read or heading section) - "start line past the end" — start_line exceeds the rendition's line count; error states the total -- 'path must end in ".md"' — the path names a non-markdown file${whenToolEnabledText("vault_read_file", "; read files (images, .canvas, data files) with vault_read_file instead")} +- 'path must end in ".md"' — add the extension to a note's path${whenToolEnabledText("vault_read_file", "; read other files (images, .canvas, data files) with vault_read_file")} - "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it - "properties block is not valid YAML …" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it +- "EACCES: …" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it -Returns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block ("path — lines 1–20 of 250 (continue with start_line: 21)"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF. +Returns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block ("path — lines 1–20 of 250 (continue with start_line: 21)"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings. Outline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings ("##" with no text) appear with text: "" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note${whenToolEnabledText("vault_replace_in_note", ", and edit via vault_replace_in_note")}.`, inputSchema: {