Skip to main content
Glama
Vault Cortex Obsidian MCP Server

Vault Cortex Obsidian MCP Server

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
LOG_LEVELNoLogging verbosity: debug, info, warn, or error.info
PUBLIC_URLNoPublic URL clients use to reach the server. Used as the OAuth issuer URL in discovery metadata.http://localhost:8000
VAULT_PATHYesAbsolute path to your Obsidian vault, mounted into the container.
INDEX_DB_PATHNoSQLite FTS5 index DB path inside the container./data/index.db
MCP_AUTH_TOKENYesBearer token for MCP client authentication. Generate with: openssl rand -hex 32

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

Read, write, and search an Obsidian vault. Use vault_search and vault_read_note to find and read notes; vault_read_file for images, canvases, and other non-markdown files. Use vault_get_memory to retrieve user preferences and context from About Me/ files. Use vault_write_note and vault_update_memory for writes.

Vault content is Obsidian Flavored Markdown. Write tools pass content through without escaping — be intentional about Obsidian syntax (#, [[, %%, etc.) in inputs.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
vault_read_noteA

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.

Example: vault_read_note({ path: "Projects/vault-cortex.md" }) Example: vault_read_note({ path: "TASKS.md", outline: true }) Example: vault_read_note({ path: "TASKS.md", heading: "Active" }) 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 })

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. For a note or section too long for one response, 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. 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.

Behavior: a section spans from its heading to the next heading with the same or a smaller level number (or EOF), so it includes its child headings. Empty headings ("##" with no text) act as boundaries but cannot be targeted; read the parent section or the full note, and edit them via vault_replace_in_note.

Errors:

  • "note not found" — no note exists at this path; verify it with vault_list_notes

  • "heading not found" — the message lists the 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 — set at most one"

  • "heading_level requires a heading" — pass heading with it

  • "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 and limit page only a full read or a heading section

  • "start line past the end" — the message states the line count; resend a start_line within it

  • 'path must end in ".md"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "properties block is not valid YAML …" — only properties_only raises it; a full read, outline and heading still work; the error says how to repair it

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

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. In the outline, bytes at the root is the whole file's size, modified its modification time (ISO 8601), and each heading's bytes the size of the text heading mode returns for that section; an empty heading appears with text: "".

vault_list_notesA

List the vault's note (.md) paths, optionally filtered by folder and/or glob pattern. Returns paths only — not content or metadata.

Example: vault_list_notes({ folder: "Projects" }) Example: vault_list_notes({ glob: "**/session-log.md" }) Example: vault_list_notes({ folder: "Projects", glob: "*.md" }) — the folder's top-level notes only

When to use: Browsing what exists in a folder by filename, or finding notes matching a path pattern. Prefer vault_search_by_folder when you need metadata (tags, type, related) along with paths. Prefer vault_search for content-based discovery. Use vault_read_note to read a note from the results.

Parameters:

  • folder names a whole folder; without a glob, notes in its subfolders are listed too: "Projects" covers "Projects/Archive" but not "ProjectsOld/". Use the folder's exact letter case; a different case can find nothing.

  • glob matches each note's path inside folder (its vault-relative path when folder is omitted), case-sensitively. * stays within one folder level and ** spans any depth: with folder "Projects", "**/*.md" lists every note under it.

Behavior: Paths come back sorted by vault-relative path in code-unit order (uppercase before lowercase). Hidden (dot-prefixed) notes and folders are never listed.

Errors:

  • A nonexistent folder or no glob matches returns an empty array, not an error.

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — use a vault-relative folder outside hidden folders, or omit folder to list the whole vault.

  • "ENAMETOOLONG: …" or another filesystem error — the message names the folder it could not read, and what to do; until then, pass a folder outside it.

Returns: JSON array of every matching vault-relative path (e.g. ["Notes/idea.md", "Projects/plan.md"]).

vault_write_noteA

Create a markdown note, or with overwrite: true replace an existing note's body. Properties merge with any existing properties when overwriting (new keys added, matching keys overwritten, keys set to null removed, unmentioned keys preserved); overwriting without properties keeps the existing property values. A new note leaves out keys set to null.

Example: vault_write_note({ path: "Projects/notes.md", body: "# Notes\n\nProject notes here.", properties: { tags: ["project"], type: "project" } }) Example: vault_write_note({ path: "Projects/notes.md", body: "Updated content.", overwrite: true })

When to use: Creating a new note, or replacing a whole note's body. Prefer vault_patch_note or vault_replace_in_note to change part of a note. Prefer vault_update_properties for property-only edits (no body round-trip). Prefer vault_update_memory for appending dated entries to About Me/ memory files.

Errors:

  • "note already exists" — set overwrite: true to replace it

  • "path must end in …" — add the .md extension

  • "cannot write note …: that path is not a file" — a folder already has this name; choose another path

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "concurrent write in progress" — a write to this note is in flight; re-read it and retry

  • "body contains a control character" — remove the character the message names

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "properties block …" — with overwrite: true, the note's properties block can't be read, or a rewrite would lose it; the error says how to repair it

  • "the note would open with a properties block …" — a note without properties would start with --- lines around text that can't be properties; the error says how to avoid it

Obsidian syntax: Body is Obsidian Flavored Markdown (no escaping applied). Watch for: #word = tag (escape with #), [[ = wikilink, %% = comment block. In properties: quote wikilink values ("[[Note]]"), use YAML lists for tags, keep property types consistent (string/number/list mismatches cause silent query failures).

Returns: "Wrote ".

vault_patch_noteA

Surgical edits to a markdown note — append, prepend, replace, or insert content by heading. Properties are kept as written, and body line breaks are saved as LF. A repeated call can write the content again, so after a timeout or lost response, re-read the note before retrying.

Example: vault_patch_note({ path: "Projects/plan.md", operation: "append", heading: "Open questions", content: "- Which region hosts the backup?" }) Example: vault_patch_note({ path: "Notes/Plan.md", operation: "replace", heading: "Status", content: "On track for launch.\n" }) Example: vault_patch_note({ path: "Notes/Plan.md", operation: "insert_before", heading: "Phase 2", content: "## Phase 1\nDone.\n" }) Example: vault_patch_note({ path: "Notes/Plan.md", operation: "prepend", content: "> [!info] Draft\n> Not reviewed yet.\n" }) — adds a leading callout

When to use: Modifying part of an existing note without overwriting the entire body. Prefer vault_write_note for a new note or a full rewrite (overwrite: true); vault_replace_in_note for an in-place text change (a typo, a rename); vault_insert_at_anchor for inserting next to a specific line; vault_replace_span for replacing a run of lines; vault_delete_span for removing lines; vault_update_properties for properties; vault_create_task for adding a task; vault_update_task for a task's status or lane.

Operations:

  • append: add content at end of section (or end of file if no heading), below any empty lines that end it. So when a blank line follows a table, an appended row starts a new table; to extend the original table, use vault_insert_at_anchor after its last row

  • prepend: add content after heading line (or at the top of the body, below the properties block, if no heading). To start a new section above the note's current first heading, use insert_before on that heading: a no-heading prepend whose content starts with a heading nests any content above the first heading inside the new section.

  • replace: replace section body (requires heading); to empty it, use vault_delete_span on its lines

  • insert_before: insert content above the heading line (requires heading)

Behavior:

  • Heading-targeted operations keep the matched heading. Every operation inserts content as whole lines and adds no empty lines of its own: content ending in a newline ("Done.\n") leaves an empty line after it, and content without one ("Done.") puts the next line directly below.

  • A section spans from its heading to the next heading with the same or a smaller level number (or EOF), so it includes its child headings. Empty headings ("##" with no text) act as boundaries but cannot be targeted; edit their content via vault_replace_in_note instead.

  • To edit a leading callout, read it via vault_read_note(outline: true), then vault_replace_in_note the old block for the new one; a no-heading prepend would stack a second callout above it.

Errors:

  • "note not found" — path does not exist; check vault_list_notes for valid paths

  • "path must end in …" — add the .md extension

  • "heading not found" — the message lists the available headings

  • "ambiguous heading" — multiple headings match; use heading_level to disambiguate; if they share a level, rename one or use vault_replace_in_note to target by text content

  • "operation … requires a heading target" — pass heading, or use append or prepend to edit the file body

  • "heading cannot be empty" — heading is whitespace only; pass the heading's text

  • "content begins with the heading … which would duplicate it" — content's first line repeats the target heading; omit it

  • "section … has N child headings …" — the target section contains child headings that replace would destroy; pass include_children: true to confirm, or target the child heading directly

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "concurrent write in progress" — a write to this note is in flight; re-read it and retry

  • "content contains a control character" — remove the character the message names

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "the note would open with a properties block …" — a note without properties would start with --- lines around text that can't be properties; the error says how to avoid it

Obsidian syntax: Content is Obsidian Flavored Markdown (no escaping applied). Watch for: #word = tag, [[ = wikilink, %% = comment block. Table rows: send only the data row ("| cell1 | cell2 |"), not the header or separator — duplicating them splits the table.

Returns: Confirmation message — "Applied to → ", where target is the matched heading (e.g. "## Active") or "file body" for a no-heading append/prepend. A no-heading prepend that nested existing content under an inserted heading adds a sentence naming the content's size and any call that would have avoided it.

vault_replace_in_noteA

Find and replace exact text in a markdown note's body. Properties are kept as written, and body line breaks are saved as LF. Edit properties via vault_update_properties or vault_write_note's properties parameter. A repeated call replaces old_text again if the note still holds it, including a copy inside the first call's new_text, so after a timeout or lost response, re-read the note before retrying.

Example: vault_replace_in_note({ path: "Projects/plan.md", old_text: "TODO: write summary", new_text: "Summary complete." }) Example: vault_replace_in_note({ path: "Projects/plan.md", old_text: "- [ ] draft outline\n", new_text: "" }) Example: vault_replace_in_note({ path: "Projects/plan.md", old_text: "Q3", new_text: "Q4", replace_all_occurrences: true })

When to use: Targeted text changes within a single location — fixing typos, updating values, renaming terms, or removing a short line (new_text=""). To delete a large multi-line block, prefer vault_delete_span (short anchors instead of full old_text). To replace a large block by anchors instead of reproducing the full old_text, use vault_replace_span. To relocate content between headings, use vault_patch_note to add at the target first, then remove from source (new_text="") — add-before-delete, so a failure duplicates the block instead of losing it.

Parameters:

  • old_text: include enough surrounding context to ensure uniqueness when the target text appears in multiple places.

  • replace_all_occurrences: set true for deliberate bulk renames or term replacements.

Behavior:

  • A non-empty new_text replaces the match exactly; new_text="" deletes it, and when text remains on the line, the line is written exactly as asked.

  • When new_text="" leaves the match's line empty, blank lines never double: the text above and below ends up separated by the wider of the two runs of empty lines that stood there, and by at least one.

  • At the note's end, where no text follows, the run of empty lines above is kept one line shorter ("A\n\nB\n" without "B" → "A\n").

  • To remove a table row or list item, include its line break in old_text, or the empty line left behind splits the table or list.

  • A line holding only spaces or tabs counts as text, not as an empty line.

Errors:

  • "note not found" — path does not exist; check vault_list_notes for valid paths

  • "path must end in …" — add the .md extension

  • "text not found" — old_text does not appear in the note body; verify exact text with vault_read_note

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "concurrent write in progress" — a write to this note is in flight; re-read it and retry

  • "new_text contains a control character" — remove the character the message names

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "the note would open with a properties block …" — a note without properties would start with --- lines around text that can't be properties; the error says how to avoid it

Obsidian syntax: new_text is Obsidian Flavored Markdown (no escaping applied). Watch for: #word = tag, [[ = wikilink, %% = comment block in replacement text.

Returns: "Replaced occurrence(s) in ".

vault_delete_spanA

Delete a contiguous block of whole lines from a note's body by referencing short anchor substrings instead of reproducing the full block text. Properties are kept as written, and body line breaks are saved as LF. With first_match: true, a repeated call deletes the next match, so after a timeout or lost response, re-read the note before retrying.

Example: vault_delete_span({ path: "Tracker.md", start_anchor: "| 2024-03-02 | Acme" }) — deletes the one table row whose line contains that fragment. Example: vault_delete_span({ path: "Notes/Plan.md", start_anchor: "> [!warning] Stale", end_anchor: "remove after launch" }) — deletes from the start anchor line through the end anchor line.

When to use: Removing a block you have already read — a table row, callout, or run of list items — where reproducing it exactly as old_text would be error-prone. Prefer vault_replace_in_note for small in-place edits. To replace a block, use vault_replace_span (one atomic step).

Parameters:

  • An anchor matches a body line (any line below the properties block).

  • end_anchor is searched at or after the start line and must be unique among those lines. If start_anchor and end_anchor match the same line, only that one line is deleted.

  • A span that includes a code block's opening fence can't end at a plain closing fence (```): the end anchor is ambiguous, and first_match: true would end the span at the opening fence; to remove such a block, use vault_replace_in_note with new_text: "".

Behavior:

  • Where the removed lines stood, blank lines never double: the text above and below ends up separated by the wider of the two runs of empty lines that stood there. So removing a table row leaves the table whole.

  • At the note's end, where no text follows, the run of empty lines above is kept one line shorter ("A\n\nB\n" without "B" → "A\n").

  • A line holding only spaces or tabs counts as text, not as an empty line.

Errors:

  • "note not found" — verify path with vault_list_notes

  • "path must end in …" — add the .md extension

  • "start anchor not found" / "end anchor not found" — no body line holds the fragment; verify with vault_read_note

  • "ambiguous start anchor …" / "ambiguous end anchor …" — the anchor matches multiple lines; use a longer fragment or set first_match: true

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "concurrent write in progress" — a write to this note is in flight; re-read it and retry

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "the note would open with a properties block …" — a note without properties would start with --- lines around text that can't be properties; the error says how to avoid it

Returns: 'Deleted line(s) from : ""' — the removed lines joined by line breaks, cut at 80 characters with "…".

vault_replace_spanA

Replace a contiguous block of whole lines in a note's body with new content, identified by short anchor substrings instead of the block's full text. Properties are kept as written, and body line breaks are saved as LF. A repeated call can write the content again, or with first_match: true replace the next match, so after a timeout or lost response, re-read the note before retrying.

Example: vault_replace_span({ path: "Tracker.md", start_anchor: "| 2024-03-02 | Acme", content: "| 2024-03-02 | Acme Corp | Updated |" }) — replaces the one table row whose line contains that fragment. Example: vault_replace_span({ path: "Notes/Plan.md", start_anchor: "> [!warning] Stale", end_anchor: "remove after launch", content: "> [!info] Current\n> Updated for v2." }) — replaces the callout block with a new one.

When to use: Replacing a block you have already read — a table row, callout, or run of list items — where reproducing it exactly as old_text would be error-prone. Prefer vault_replace_in_note for small in-place text changes (typos, renaming). Prefer vault_delete_span when removing without replacement.

Parameters:

  • An anchor matches a body line (any line below the properties block).

  • end_anchor is searched at or after the start line and must be unique among those lines. If start_anchor and end_anchor match the same line, only that one line is replaced.

  • A span that includes a code block's opening fence can't end at a plain closing fence (```): the end anchor is ambiguous, and first_match: true would end the span at the opening fence; to replace such a block, use vault_replace_in_note.

Behavior:

  • Each line break at content's start or end becomes an empty line on that side, so content ending in one ("| X |\n") leaves an empty line after it, which splits a table.

  • Where the note already has empty lines next to content's start or end, the wider run (the note's or content's) is kept, so blank lines never double. At the note's end, content's trailing line breaks count one fewer ("| X |\n" leaves no empty line).

  • Empty lines inside content are kept as written, and a line holding only spaces or tabs counts as text, not as an empty line.

Errors:

  • "note not found" — verify path with vault_list_notes

  • "path must end in …" — add the .md extension

  • "start anchor not found" / "end anchor not found" — no body line holds the fragment; verify with vault_read_note

  • "ambiguous start anchor …" / "ambiguous end anchor …" — the anchor matches multiple lines; use a longer fragment or set first_match: true

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "concurrent write in progress" — a write to this note is in flight; re-read it and retry

  • "content contains a control character" — remove the character the message names

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "the note would open with a properties block …" — a note without properties would start with --- lines around text that can't be properties; the error says how to avoid it

Obsidian syntax: content is Obsidian Flavored Markdown (no escaping applied). Watch for: #word = tag, [[ = wikilink, %% = comment block.

Returns: Confirmation message "Replaced line(s) with line(s) in " — N counts the lines the span covered, M the lines in content as sent (its line breaks plus one).

vault_insert_at_anchorA

Insert content as whole lines into a note's body, before or after a specific line identified by a short anchor substring. Properties are kept as written, and body line breaks are saved as LF. A failed save leaves the note unchanged. A repeated call inserts the content again, or fails as ambiguous when the content holds the anchor, so after a timeout or lost response, re-read the note before retrying.

Example: vault_insert_at_anchor({ path: "Tracker.md", anchor: "| 2024-03-02 | Acme", position: "after", content: "| 2024-03-03 | Beta Corp | New entry |" }) — inserts a new table row after the matched row. Example: vault_insert_at_anchor({ path: "Notes/Plan.md", anchor: "## Phase 2", position: "before", content: "> [!note] Phase 1 must close before this starts.\n" }) — inserts a callout and a blank line above the Phase 2 heading.

When to use: Adding content at a precise location identified by a nearby line's text, without needing to know the heading structure. Good for inserting rows into tables, adding items into lists at a specific position, or placing content relative to a known landmark line. Prefer vault_patch_note for heading-targeted inserts (append/prepend to a section). Prefer vault_replace_span when replacing a block rather than inserting next to it.

Parameters:

  • An anchor matches a body line (any line below the properties block).

  • content is inserted verbatim — blank lines inside it are kept, and a trailing newline adds a blank line after the inserted block.

Errors:

  • "note not found" — verify path with vault_list_notes

  • "path must end in …" — add the .md extension

  • "anchor not found" — no body line holds the fragment; verify with vault_read_note

  • "ambiguous anchor …" — the anchor matches multiple lines; use a longer fragment or set first_match: true

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "concurrent write in progress" — a write to this note is in flight; re-read it and retry

  • "content contains a control character" — remove the character the message names

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "the note would open with a properties block …" — a note without properties would start with --- lines around text that can't be properties; the error says how to avoid it

Obsidian syntax: content is Obsidian Flavored Markdown (no escaping applied). Watch for: #word = tag, [[ = wikilink, %% = comment block.

Returns: Confirmation message "Inserted line(s) <before|after> anchor in " — N counts the lines in content as sent (its line breaks plus one).

vault_delete_noteA

Delete a markdown note, moving it to the vault's .trash/ folder or removing it for good as the vault's Obsidian "Deleted files" setting directs.

Example: vault_delete_note({ path: "Scratch/temp.md" }) Example: vault_delete_note({ path: "Archive/2024/old.md", prune_empty_folders: true }) — also remove "Archive/2024" (and "Archive") if deleting the note empties them.

When to use: Removing a note you no longer need. Prefer vault_delete_memory for removing individual dated entries from About Me/ memory files. To relocate a note, use vault_move_note instead. To replace a note's content, use vault_write_note with overwrite: true instead.

Behavior:

  • The "Deleted files" setting decides the outcome, which the caller can't choose or see in advance:

    • "Move to system trash" moves the note to .trash/. Notes this server moved there are deleted after its retention period (TRASH_RETENTION_DAYS: 30 days by default, never when set to none).

    • "Move to Obsidian trash" moves the note to .trash/ and keeps it forever.

    • "Permanently delete" removes the note for good.

  • Links to the note from other notes become broken; list them first with vault_get_backlinks. Protected paths are refused: About Me/ and the daily notes folder (Daily Notes/ by default; vault_get_daily_note's path shows the folder in use; not protected when Obsidian's Daily Notes folder setting is the vault root or blank).

Parameters:

  • prune_empty_folders keeps a folder that holds any file, even a hidden .DS_Store. Pruning is best-effort: a folder that can't be removed never fails the call.

Errors:

  • "cannot delete protected path" — the path sits under a protected folder; use vault_delete_memory for a memory entry, or ask the vault's owner to delete the note in Obsidian

  • "path must end in …" — add the .md extension

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "concurrent write in progress" — another write to this note is in flight; retry

  • "note not found: …" — verify path with vault_list_notes

  • "cannot move to trash … — 100 collisions in .trash/" — .trash/ holds this name and 100 numbered copies; ask the vault's owner to clear old copies, then retry

  • "cannot move to trash …" without "100 collisions" — the note stays put; ask the vault's owner to fix .trash/ (e.g. a plain file blocks a needed folder), then retry

  • "cannot delete …" (other than a protected path) — the note stays put; ask the vault's owner to fix the cause (e.g. permissions), then retry

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do; the note stays put

  • "cannot read trash config from .obsidian/app.json" — ask the vault's owner to repair it, then retry

  • "cannot read daily notes config from .obsidian/daily-notes.json" — the file that names the protected daily notes folder can't be parsed; ask the vault's owner to repair it, or the server's operator to set DAILY_NOTES_FOLDER or PROTECTED_PATHS, then retry

Returns: Confirmation message naming the outcome — "Deleted " for permanent removal, "Moved to trash (<.trash/ path>)" when the note landed in .trash/. Notes how many empty folders were pruned when any were.

vault_move_noteA

Move or rename a note and rewrite every link across the vault that points to it, like Obsidian's built-in rename. Incoming links in other notes — [[wikilinks]], [[wikilink|aliases]], [[wikilink#headings]], ![[embeds]], markdown, and links in properties (e.g. related:) — and Canvas cards that embed the note are updated to the new path; the moved note's own relative links are fixed so they still resolve from the new folder, including relative links to attachments (e.g. ![[../assets/photo.png]]). A link is only rewritten when leaving it unchanged would break it, so a short [[Note]] that stays unambiguous after a folder move is left alone.

Example: vault_move_note({ old_path: "Inbox/Draft.md", new_path: "Inbox/Spec.md" }) — pure rename. Example: vault_move_note({ old_path: "Inbox/spec.md", new_path: "Inbox/Spec.md" }) — case-only rename; works even where the filesystem treats both spellings as one file. Example: vault_move_note({ old_path: "Inbox/Spec.md", new_path: "Projects/Spec.md" }) — move to another folder. Example: vault_move_note({ old_path: "Inbox/Spec.md", new_path: "Projects/Spec.md", prune_empty_folders: true }) — also remove "Inbox" if the move empties it.

When to use: Renaming a note or relocating it to a different folder while keeping the link graph intact. Prefer this over vault_write_note + vault_delete_note, which would orphan every backlink. To only change a note's body or properties, use vault_patch_note or vault_update_properties. Protected paths can be neither moved nor moved into: About Me/ and the daily notes folder (Daily Notes/ by default; vault_get_daily_note's path shows the folder in use; not protected when Obsidian's Daily Notes folder setting is the vault root or blank).

Parameters:

  • prune_empty_folders keeps a folder that holds any file, even a hidden .DS_Store. An in-place rename or a move into a subfolder of the source prunes nothing. Pruning is best-effort: a folder that can't be removed never fails the call.

Errors:

  • "destination exists: …" — this tool never overwrites; pick a free new_path or delete the existing note first.

  • "note not found: …" — old_path does not exist; verify path with vault_list_notes.

  • "source and destination are the same path" — nothing to move; pass a different new_path.

  • "cannot move protected path …" / "cannot move into protected path …" — old_path or new_path sits under a protected folder; pick a new_path outside it, or leave a protected note where it is.

  • "cannot read daily notes config from .obsidian/daily-notes.json" — the file that names the protected daily notes folder can't be parsed; ask the vault's owner to repair it, or the server's operator to set DAILY_NOTES_FOLDER or PROTECTED_PATHS, then retry.

  • "path must end in …" — add the .md extension.

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — use vault-relative paths with no hidden (dot-prefixed) file or folder in them.

  • "concurrent write in progress" — a write is in flight; retry the move.

  • "backlink set did not stabilize" — the notes linking to the note kept changing while the move prepared; nothing was written; retry the move.

  • "move aborted: could not read …" — names the note, or a note linking to it, that could not be read; nothing was written. Fix that note, then retry.

  • "move aborted: could not rewrite …" — says why a note could not be saved with its links rewritten, and how to fix it; nothing was written.

  • "move aborted: could not write to …" / "move aborted: could not rename to …" (a case-only rename) — new_path could not be created, and new parent folders it needed may remain; retry once, then ask the vault's owner to fix the folder or disk.

  • "move incomplete: …" — the move failed partway; no data is lost, and the message names what failed, the state it left, and the step that finishes the move.

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do; nothing was written.

  • "[SqliteError]: …" — the search index failed and nothing was written; the message says what to do.

Obsidian syntax: Link rewrites preserve each link's existing form — embed marker (!), heading anchor (#…), and alias (|…) are kept; a markdown link keeps its original extension and link text.

Returns: JSON with moved_to (the new path), links_updated (count of link occurrences rewritten, including the moved note's own relative links), updated_notes (sorted paths of the other notes and Canvas files that were edited; the moved note is implied by moved_to), and pruned_empty_folders (count of empty parent folders removed).

vault_update_propertiesA

Update a note's properties. By default they merge shallowly — new keys added, matching keys overwritten, null deletes a key, unmentioned keys preserved. With replace: true they replace the whole properties block: here null writes a property with no value, and {} removes the whole block. The body is kept.

Example: vault_update_properties({ path: "Projects/todo.md", properties: { status: "active", draft: null } }) Example: vault_update_properties({ path: "Projects/todo.md", properties: { title: "Todo", status: "active" }, replace: true })

When to use: Changing tags, status, type, or any property without reading/rewriting the full note body. Prefer vault_write_note when creating a new note, or replacing the body (with overwrite: true). Read current properties first with vault_read_note({ properties_only: true }), or the full note when repairing a properties block the server refused. Arrays are replaced entirely, not appended to.

Errors:

  • "note not found" — path does not exist; create the note first with vault_write_note

  • "path must end in …" — add the .md extension

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "concurrent write in progress" — a write to this note is in flight; re-read it and retry

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "properties block …" — without replace: true, the merge refuses a block it cannot keep: invalid YAML, a list, a single value, or a YAML type tag such as !!timestamp; the error says how to repair it

  • "the note would open with a properties block …" — a note without properties would start with --- lines around text that can't be properties; the error says how to avoid it

Obsidian syntax: Use arrays for multi-value fields (tags: [a, b]), quote wikilinks ("[[Note]]"), keep types consistent (mismatches cause silent query failures).

Returns: "Updated properties on ", or "Replaced properties on " with replace: true.

vault_searchA

Hybrid search across vault notes and file content (canvas, PDF, and text files), ranked by combined keyword and semantic relevance. Semantic matching finds notes even when exact keywords differ — "career aspirations" finds notes about "goals" and "targets". Unquoted terms use implicit AND with stemming ("running" also matches "run"); wrap in double quotes for exact phrases; punctuated terms (vault-cortex, deploy/local) are matched as exact adjacent-word phrases.

Filters — all conditions AND-combine with each other and the text query; any filter but folder returns notes only:

  • folder: a whole folder, subfolders included: "Projects" covers "Projects/Archive" but not "ProjectsOld/", ignoring ASCII letter case

  • tags: every listed tag must match, in the tags property or as a #tag in the body, in any letter case; "project" also matches "project/a"

  • type: the type property, matched exactly, letter case included

  • related: every listed link must appear in the related property, written as it is there, brackets and alias included

  • properties: keys and text values match exactly, letter case included; values compare by exact type — pass a number as a number, not "4". Exception: true and false compare as 1 and 0, so true or 1 matches a checked checkbox and the number 1. A list property matches when any element equals the value.

  • created / modified: bounds compare whole calendar days in the server's time zone (returned created and modified values show its UTC offset). A note whose created property is missing or not an ISO date never matches a created filter

Example: vault_search({ query: "how the server watches for file changes" }) Example: vault_search({ query: "kubernetes networking", filters: { tags: ["reference"] } }) Example: vault_search({ query: "meeting notes", filters: { type: "meeting", folder: "Work" } }) Example: vault_search({ query: "decision", filters: { modified: { after: "2026-06-30" } } }) — matching notes touched in July or later

When to use: The primary discovery tool for content-based queries, optionally constrained by metadata. Prefer vault_search_by_tag for tag-only queries without text. Prefer vault_search_by_folder for browsing a folder. Prefer vault_search_by_property for metadata-only queries. Prefer vault_recent_notes for time-based browsing.

Behavior: Results can lag a file change by a few seconds.

Errors:

  • No matches returns an empty results array with total 0, not an error

  • Malformed query syntax never throws: the words AND, OR, NOT and NEAR in any letter case, and punctuation such as *, ^, parentheses and colons, are dropped before keyword matching; a query left with no terms is matched by meaning only, or finds nothing when only keyword matching ran (search_mode "fts")

  • "invalid created.on date: …" or "… is not a calendar date" (any created/modified bound) — pass a real calendar date in YYYY-MM-DD form

  • "tag must not be empty …" — a tags entry is only "#"; pass the tag name

  • "[SqliteError]: …" / "[IndexIntegrityError]: …" — the index failed, not the call; the message says what to do

Returns: JSON with results array (path, title, snippet, score, tags (the tags property; vault_update_properties edits it), body_tags (#tags in the note's text, one per spelling; vault_replace_in_note edits them; absent when none), folder (a note's top-level folder; a file's full parent folder), type, kind, extension, created, modified, bytes), total (results returned, not all matches), search_mode ("hybrid" when vector embeddings contributed, "fts" when only keyword matching ran), and reranked (true when a second ranking pass reordered the results). A higher score is more relevant. kind is "note" or, for non-markdown content, "file", which also carries extension (e.g. ".pdf"). created is omitted, not null, when a result has none; type is null when the result has no type property; bytes is the on-disk size. With include_leading_callout, each result also carries leading_callout (the opening callout, { type, title, body }) when present.

vault_search_by_tagA

Find notes by tag, in the tags property or as a #tag in the body, ignoring letter case. Unless exact is true, a tag also matches every tag nested under it at any depth: "project" matches "project/vault-cortex" and "project/a/b". A nested tag continues with "/", so "project" never matches "projects" or "my-project".

Example: vault_search_by_tag({ tag: "project" }) Example: vault_search_by_tag({ tag: "project", exact: true, limit: 50 }) — notes tagged "project" itself, up to 50

When to use: Tag-only lookups, for one tag or a whole tag hierarchy, with no text query. Use vault_list_tags first to discover available tags. Prefer vault_search when you need text-based relevance ranking.

Parameters:

  • tag: a trailing slash matches nothing ("project/" matches neither "project" nor "project/a"), so pass the tag name without it.

  • limit keeps the most recently modified notes; nothing in the response signals truncation; raise limit for more.

Behavior: Results can lag a file change by a few seconds.

Errors:

  • An unknown tag or no matches returns an empty array, not an error.

  • "tag must not be empty …" — tag is "#" alone; pass the tag name.

  • "[SqliteError]: …" / "[IndexIntegrityError]: …" — the index failed, not the call; the message says what to do

Returns: JSON array of note metadata: path, title (the title property; file name without .md when missing or not text), tags (the tags property; vault_update_properties edits it), body_tags (#tags in the note's text, one per spelling; vault_replace_in_note edits them; absent when none), related ([] when missing), folder (top-level, "" at the root), type (null when missing or not text), created (null when missing or not an ISO date), modified (ISO 8601 file time), bytes (on-disk size), and when present leading_callout (the opening callout, { type, title, body }) and additional_properties (every property but title, type, created, related and tags). Tags keep the note's letter case. Sorted by most recently modified, then by path.

vault_list_tagsA

List all tags in the vault with note counts, ordered by count descending, then by tag name. Tags in the tags property and #tags written in note bodies both count. Spellings that differ only in letter case are one tag, shown as the spelling written most often. Each hierarchical tag appears as one full entry, not split into segments. Count is unique notes, not occurrences.

Example: vault_list_tags() returns [{ tag: "session-log", count: 42 }, { tag: "project/vault-cortex", count: 8 }, ...]

When to use: Discovering what tags exist before searching by tag. Good first step for vault orientation. Prefer vault_search_by_tag once you know which tag to query. To count tags property values in one folder, or only the most-used ones, use vault_list_property_values({ key: "tags" }) with folder or limit; it does not count #tags in note bodies.

Errors:

  • A vault with no tagged notes returns an empty array, not an error.

  • "[SqliteError]: …" — the index failed, not the call; the message says what to do

Returns: JSON array of { tag, count } holding every tag in the vault; tag omits the "#" prefix.

vault_recent_notesA

List recently modified or created notes, sorted by timestamp — a time-ordered window into the vault, not a date-range filter.

Example: vault_recent_notes({ limit: 10 }) Example: vault_recent_notes({ sort_by: "created", limit: 5 })

When to use: Catching up on vault changes, finding recent work, or orienting after a break. Prefer vault_search for content-based discovery, including a text query limited to a date range (its created and modified filters). Prefer vault_search_by_folder for browsing a specific folder.

Parameters:

  • sort_by "modified" uses filesystem mtime. Any file write counts (content edits, property changes, sync touches), so recently-synced notes appear recent even without user edits.

  • sort_by "created" uses the created property; notes where it is missing or not an ISO date sort after every dated note, so a small limit can leave them out — use "modified" to see them.

Behavior: Results can lag a file change by a few seconds.

Errors:

  • An empty vault returns an empty array, not an error.

  • "[SqliteError]: …" / "[IndexIntegrityError]: …" — the index failed, not the call; the message says what to do

Returns: JSON array of note metadata: path, title (the title property; file name without .md when missing or not text), tags (the tags property; vault_update_properties edits it), body_tags (#tags in the note's text, one per spelling; vault_replace_in_note edits them; absent when none), related ([] when missing), folder (top-level, "" at the root), type (null when missing or not text), created (null when missing or not an ISO date), modified (ISO 8601 file time), bytes (on-disk size), and when present leading_callout (the opening callout, { type, title, body }) and additional_properties (every property but title, type, created, related and tags). Newest first by the sort_by timestamp.

vault_search_by_folderA

Browse notes in a folder with full metadata.

Example: vault_search_by_folder({ folder: "Projects" }) Example: vault_search_by_folder({ folder: "About Me", recursive: false, limit: 10 }) — the folder's own notes, 10 most recent

When to use: Exploring a folder's contents with full context for vault orientation. Prefer vault_list_notes when you only need paths. Prefer vault_search when you have a text query. Use vault_get_backlinks or vault_get_outgoing_links to explore how notes in a folder connect to the rest of the vault.

Parameters:

  • folder names a whole folder, subfolders included: "Projects" covers "Projects/Archive" but not "ProjectsOld/", ignoring ASCII letter case; a trailing slash is ignored, and recursive: false keeps only the folder's own notes.

  • limit keeps the most recently modified notes. There is no offset, and nothing in the response signals truncation; raise limit for more.

Behavior: Results can lag a file change by a few seconds. Notes in hidden (dot-prefixed) folders never appear.

Errors:

  • An empty or nonexistent folder returns an empty array, not an error.

  • "[SqliteError]: …" / "[IndexIntegrityError]: …" — the index failed, not the call; the message says what to do

Returns: JSON array of note metadata: path, title (the title property; file name without .md when missing or not text), tags (the tags property; vault_update_properties edits it), body_tags (#tags in the note's text, one per spelling; vault_replace_in_note edits them; absent when none), related ([] when missing), folder (top-level, "" at the root), type (null when missing or not text), created (null when missing or not an ISO date), modified (ISO 8601 file time), bytes (on-disk size), and when present leading_callout (the opening callout, { type, title, body }) and additional_properties (every property but title, type, created, related and tags). Sorted by most recently modified, then by path.

vault_list_property_keysA

Discover all property keys in the vault with note counts and sample values, without reading individual notes.

Example: vault_list_property_keys() returns [{ key: "tags", count: 342, sample_values: ["session-log", "project"] }, ...] Example: vault_list_property_keys({ folder: "Projects" }) — the keys used under Projects/ only

When to use: Discovering what properties exist before searching by property. Good first step for vault orientation alongside vault_list_tags. Prefer vault_list_property_values when you need the full list of values for a specific key. Prefer vault_search_by_property to find notes matching a specific key-value pair.

Parameters:

  • folder names a whole folder, subfolders included: "Projects" covers "Projects/Archive" but not "ProjectsOld/", ignoring ASCII letter case; omit it to scan the entire vault.

Behavior: Results can lag a file change by a few seconds.

  • Only properties count; inline Dataview fields (key:: value) are not listed.

  • count is the number of notes that have the key, including notes where its value is empty (null).

  • sample_values are the key's 3 most frequent values as text (a number as JavaScript prints it, so 1.0 lists as "1"; a checkbox as "1" or "0"), counting each array element separately; null values are skipped.

Errors:

  • An empty vault or folder returns an empty array, not an error.

  • "[SqliteError]: …" — the index failed, not the call; the message says what to do

Returns: JSON array of { key, count, sample_values } sorted by count descending, then by key.

vault_list_property_valuesA

List distinct values for a specific property key with how often each occurs.

Example: vault_list_property_values({ key: "status" }) returns [{ value: "done", count: 211 }, { value: "active", count: 47 }, ...] Example: vault_list_property_values({ key: "tags", limit: 10 }) — the 10 most-used tags property values

When to use: Enumerating possible values for a property key before calling vault_search_by_property. Call vault_list_property_keys first to discover valid key names. A value listed here can be passed straight to vault_search_by_property, which matches text exactly and a complete number numerically too, so value "1" finds the number 1, the text "1", and a checked checkbox.

Parameters:

  • key is case-sensitive and must match exactly as returned by vault_list_property_keys.

  • folder names a whole folder, subfolders included: "Projects" covers "Projects/Archive" but not "ProjectsOld/", ignoring ASCII letter case.

  • limit keeps the most-used values; nothing in the response signals truncation; raise limit for more.

Behavior: Results can lag a file change by a few seconds.

  • Array elements (tags: ["a", "b"]) are counted individually, so the sum of counts may exceed the note count.

  • Each value is listed as text, and values with the same text share one row with combined occurrence counts: number 1 and text "1" count together, while text "1.0" stays separate.

  • Checkbox true and false come back as "1" and "0", counted with the numbers 1 and 0.

  • null values are skipped.

Errors:

  • An unknown key or empty folder returns an empty array, not an error.

  • "[SqliteError]: …" — the index failed, not the call; the message says what to do

Returns: JSON array of { value, count } sorted by count descending, then by value in binary text order ("10" before "2").

vault_search_by_propertyA

Find notes where a property matches a value.

Example: vault_search_by_property({ key: "status", value: "in-progress" }) Example: vault_search_by_property({ key: "type", value: "session-log", folder: "Code Projects" })

When to use: Finding notes by metadata when you don't have a text query. Prefer vault_search when you also have a text query (it supports property filters too). Prefer vault_search_by_tag for tag-specific queries (it matches nested tags and #tags in note bodies; key "tags" here matches only the tags property). Use vault_list_property_keys to discover valid keys and vault_list_property_values to see what values a key takes.

Parameters:

  • key and value both match exactly and case-sensitively, with no partial matching or globbing; value matches a stored text value as written.

  • A value written as a complete, finite YAML number (-4, 04, .5, 4., 1e3, 0x1F, 0o17) also matches stored numbers numerically: "04" and "4.0" match number 4 and their own literal text, but not text "4". Anything else ("4abc", " 4", 0b binary) matches only as text.

  • A checkbox matches only as text: pass "1" (checked) or "0" (unchecked); "1.0" and "true" do not match.

  • A list property matches when an element equals value in full: "blog" matches tags: ["blog", "draft"] but not tags: ["my-blog"].

  • folder names a whole folder, subfolders included: "Projects" covers "Projects/Archive" but not "ProjectsOld/", ignoring ASCII letter case; omit it to search the entire vault.

  • limit keeps the most recently modified matches; nothing in the response signals truncation; raise limit for more.

Behavior: Results can lag a file change by a few seconds.

Errors:

  • An unknown key or unmatched value returns an empty array, not an error.

  • "[SqliteError]: …" / "[IndexIntegrityError]: …" — the index failed, not the call; the message says what to do

Returns: JSON array of note metadata: path, title (the title property; file name without .md when missing or not text), tags (the tags property; vault_update_properties edits it), body_tags (#tags in the note's text, one per spelling; vault_replace_in_note edits them; absent when none), related ([] when missing), folder (top-level, "" at the root), type (null when missing or not text), created (null when missing or not an ISO date), modified (ISO 8601 file time), bytes (on-disk size), and when present leading_callout (the opening callout, { type, title, body }) and additional_properties (every property but title, type, created, related and tags). Sorted by most recently modified (any file write, so a recently-synced note can sort ahead of an older edit), then by path.

vault_get_backlinksA

Find all notes and canvases that link to a given note or canvas — captures [[wikilinks]], markdown, ![[embeds]], wikilinks inside properties (e.g. related:), and canvas cards that embed the file. Heading anchors ([[note#heading]]) and aliases ([[note|alias]]) resolve as backlinks to the base note. Links inside code blocks are ignored; a note linking to itself appears in its own backlinks.

Example: vault_get_backlinks({ path: "Projects/vault-cortex.md" }) Example: vault_get_backlinks({ path: "Diagrams/architecture.canvas" })

When to use: Understanding what references a note or canvas, assessing its connectivity before editing or deleting, or finding related notes via the graph. For outgoing links (what a note links TO), use vault_get_outgoing_links. To find notes nothing else links to, use vault_find_orphans.

Parameters:

  • path: pass the target's own path, never a link's text: a link written as [[Note]], as a relative ../Note.md, or as a full path counts toward the note it resolves to.

Returns: JSON with path (the queried note or canvas), backlinks (array of { path, title, bytes } sorted by title; bytes is the source's on-disk size), and count.

Errors:

  • "path must end in …" — add the .md or .canvas extension

  • A path nothing links to returns an empty result (count 0), not an error; links from a note written or changed in the last few seconds may not appear yet. If you expected links, find valid paths with vault_search, vault_list_notes, or vault_list_files.

  • "[SqliteError]: …" — the index failed, not the call; the message says what to do

vault_get_outgoing_linksA

Find all notes and files a given note or canvas links to. For notes: [[wikilinks]], markdown, ![[embeds]], and wikilinks inside properties (e.g. related:). For canvas files: file nodes, not links inside text cards. Links inside code blocks are ignored; self-links are included. Each target is listed once, however often the note links to it.

Example: vault_get_outgoing_links({ path: "Projects/vault-cortex.md" }) Example: vault_get_outgoing_links({ path: "Diagrams/architecture.canvas" })

When to use: Navigating the graph forward, auditing broken links in one note or canvas, or checking dependencies before editing. For incoming links (what links TO a note), use vault_get_backlinks.

Behavior: Results can lag a file change by a few seconds.

Returns: JSON with path, outgoing_links (array of { path, title, exists, kind, bytes, daily_note_forward_ref } sorted by target path), and count. A target's path is the vault-relative path the link resolved to; a broken link's path is its target text without any #heading or |alias, with a markdown link's %-escapes decoded. When exists is true, kind "note" is a markdown note readable via vault_read_note and kind "file" a non-markdown file (.canvas, image, PDF) readable via vault_read_file; when exists is false the link is broken and kind is "note" even for a path like "diagram.png". title is null for files and broken links. daily_note_forward_ref is true on a broken link into the daily notes folder (a daily note not yet created, not breakage) and false otherwise, including when Obsidian's Daily Notes folder setting is the vault root or blank. bytes is the on-disk size (null for broken links), not the delivery cost: vault_read_file downscales a large image to fit response limits.

Errors:

  • "path must end in …" — add the .md or .canvas extension

  • A path that does not exist, or one in a dot-prefixed folder, returns an empty result (count 0), not an error, just as a note with no outbound links does; a note or canvas created or moved in the last few seconds may not appear yet. If you expected links, find valid paths with vault_search, vault_list_notes, or vault_list_files.

  • "[SqliteError]: …" — the index failed, not the call; the message says what to do

vault_find_orphansA

Find notes with no incoming links from other notes or canvases. A note that only links to itself still counts as an orphan.

Example: vault_find_orphans({}) Example: vault_find_orphans({ exclude_folders: ["Templates", "Archive"], limit: 20 })

When to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note. Prefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.

Parameters:

  • exclude_folders replaces the defaults, it does not add to them — list a default yourself to keep it. Pass [] for no exclusions. Each entry names a whole folder, subfolders included: "Projects" covers "Projects/Archive" but not "ProjectsOld/", ignoring ASCII letter case. Excluded notes are left out of the results; links from them still count as incoming links. To keep the daily notes folder excluded, list the folder vault_get_daily_note's path starts with.

  • limit applies after exclusions and sorting; nothing in the response signals truncation; raise limit for more.

Behavior: Results can lag a file change by a few seconds.

Errors:

  • An empty array means no orphans were found (after exclusions), not an error.

  • "too many excluded folders" — pass at most 994 exclude_folders entries, then retry.

  • "[SqliteError]: …" / "[IndexIntegrityError]: …" — the index failed, not the call; the message says what to do

Returns: JSON array of note metadata: path, title (the title property; file name without .md when missing or not text), tags (the tags property; vault_update_properties edits it), body_tags (#tags in the note's text, one per spelling; vault_replace_in_note edits them; absent when none), related ([] when missing), folder (top-level, "" at the root), type (null when missing or not text), created (null when missing or not an ISO date), modified (ISO 8601 file time), bytes (on-disk size), and when present leading_callout (the opening callout, { type, title, body }) and additional_properties (every property but title, type, created, related and tags). Sorted by most recently modified.

vault_get_memoryA

Read semantic memory from About Me/ files. Each file holds dated bullet entries under H2 headings. With file: that file. With file+section: just that H2 section's entries. No args: every file, joined by --- lines — can be large.

With file+on_or_after: only the entries dated on or after that date, in document order (newest first by default), from one H2 section when section is given and otherwise from every section.

Example: vault_get_memory({ file: "Principles" }) — the whole file Example: vault_get_memory({ file: "Principles", section: "Decision heuristics (newest first)" }) Example: vault_get_memory({ file: "Opinions", section: "Code patterns", on_or_after: "2026-09-01" }) Example: vault_get_memory({ file: "Opinions", on_or_after: "2026-09-01" }) — every section's entries since the date

When to use: Reading user preferences, principles, opinions, or other persistent context stored in About Me/ files. Call vault_list_memory_files first to discover valid file and section names. Prefer vault_read_note for reading non-memory notes. To find entries by topic across every file, use vault_memory_recall.

Errors:

  • "section requires a file" / "on_or_after requires a file" — add file

  • "memory file not found" — call vault_list_memory_files to discover valid names

  • "memory file must not start with a dot" / "… must be a bare name without path separators" / "… must be named without .md" — each message names the part of the name to drop

  • "section not found: …" — no H2 heading matches; the message lists the file's sections

  • "date must be a real ISO calendar date" — on_or_after must be a valid YYYY-MM-DD date

  • "cannot read memory file …" / "cannot list memory folder …" — a memory file, or the About Me/ folder itself on a call without file, can't be read: check file against vault_list_memory_files and shorten an over-long name; otherwise ask the vault's owner to check the folder

Returns: Without on_or_after, raw markdown with the properties block stripped, or an empty string when no memory files exist yet. With on_or_after, JSON { entries, total, on_or_after } where each entry is { file, section, date, text } — text is the raw entry markdown (bullet + continuation lines, wikilinks intact). An empty match returns { entries: [], total: 0, on_or_after }.

vault_list_memory_filesA

Discovery tool — lists About Me/ memory files with each file's heading outline, per-section entry counts, and leading callout. Does NOT return actual entries.

Example: vault_list_memory_files()

When to use: Discovering what memory files and sections exist — and what each file is for — BEFORE calling vault_get_memory, vault_update_memory, or vault_delete_memory, and each file's entry_policy to check before pruning entries. To find entries by topic instead, use vault_memory_recall.

Behavior: Lists every .md file directly inside About Me/, sorted by file name. Subfolders are not read, and hidden (dot-prefixed) files are skipped.

Errors:

  • An empty or nonexistent memory folder returns an empty array, not an error.

  • "cannot list memory folder …" — a file stands at the memory folder's path, or its permissions block the server; ask the vault's owner to fix it

  • "cannot read memory file …" — one listed file can't be read, which fails the whole listing; retry once, then ask the vault's owner to check that file

Returns: JSON array of file outlines, each { file, title, bytes, entry_policy, leading_callout, headings } — file is the name the other memory tools take as file (no .md); title is the title property, or file when there is none; bytes is the on-disk file size; headings lists H1 and H2 headings in order, each { level, text }, with entry_count on H2s; leading_callout is the top-of-file callout ({ type, title, body }; by convention a "Scope of this file" block describing what belongs in the file), or null; entry_policy is "append-only" (the default: by convention, entries are never edited or deleted) or "living" (a current-state file whose expired entries may be pruned; declared by an entry-policy property). The server does not enforce either policy.

vault_memory_recallA

Recall memory entries about a topic — entry-granular hybrid (keyword + semantic) retrieval across ALL About Me/ files and ALL time. Returns the relevant dated entries, up to limit, sorted oldest-first — semantic matching finds early entries even when their phrasing differs from the query. Tuned for recall over precision: expect some marginal entries. Content-word queries ("testing philosophy", "sustainable pacing") rank best. A meta-framed query ("opinions on testing") that scores below the relevance threshold degrades to any-term keyword matching instead of returning nothing.

Example: vault_memory_recall({ query: "working hours and pacing" }) Example: vault_memory_recall({ query: "testing philosophy", file: "Opinions" })

When to use: Answering "what does my memory say about X?" or "how has my view on Y evolved?" — topic-based recall across memory files. Prefer vault_get_memory to read a known file or section verbatim; prefer vault_search for notes outside the memory layer.

Behavior: A memory file change takes a few seconds to appear here.

Errors:

  • No matching entries returns entries: [] and total: 0, not an error

  • An unknown file returns empty results — call vault_list_memory_files to discover valid names

  • "[SqliteError]: …" — the index failed, not the call; the message says what to do

Returns: JSON { entries, total, truncated, search_mode, reranked }. Each entry is { file, section, date, text } — text is the raw entry markdown (wikilinks intact, continuation lines included); file and section feed directly into vault_get_memory or vault_delete_memory. The total counts every matched entry; truncated=true means limit dropped the least-relevant matches, whatever their dates, so raise limit or narrow the query for the complete set. With file, limit keeps the most relevant matches from that file. search_mode is "hybrid" when semantic matching contributed and "fts" when keyword matching alone did, the any-term fallback included; reranked is true when the reranker ran.

vault_update_memoryA

Append a dated entry to a section of a memory file in About Me/. An exact duplicate (same date + text in the same section) is a no-op, so retrying a timed-out call is safe. Memory files are append-only by default: when a preference changes, append the new state (newest wins) rather than deleting the old one. A file may declare entry-policy: living in its properties (surfaced by vault_list_memory_files) — a current-state file where pruning expired entries with vault_delete_memory is expected maintenance.

Example: vault_update_memory({ file: "Opinions", section: "Code patterns (newest first)", entry: "Prefer immutable data structures" }) Example: vault_update_memory({ file: "Routines", section: "Daily/weekly rhythm (newest first)", entry: "Gym Mon/Wed at 6am", options: { date: "2026-10-01", position: "bottom" } }) — a backdated entry placed below the existing ones

When to use: Recording a new preference, principle, opinion, or fact about the user. Call vault_list_memory_files first and reuse existing file and section names so entries stay grouped. Prefer vault_write_note for creating non-memory notes. A missing file or section is created automatically.

Parameters:

  • options.date only sets the bullet's date; placement follows options.position, never the date.

Obsidian syntax: Entry text is Obsidian Flavored Markdown. Watch for: #word = tag, [[ = wikilink. Escape with # or backticks when unintentional.

Errors:

  • "entry must be a single line" / "section must be a single line" — the message names the fix.

  • "date must be a real ISO calendar date" — pass options.date as an existing calendar date in bare YYYY-MM-DD form, not a timestamp.

  • "entry contains a control character" / "section contains a control character" — remove the character the message names.

  • "memory file must not start with a dot" / "… must be a bare name without path separators" / "… must be named without .md" — each message names the part of the name to drop

  • "section not created: … is nearly identical to existing section …" — pass the exact existing heading (listed in the error) to append there, or choose a clearly different name for a new section.

  • "cannot read memory file …" — the file can't be read: check file against vault_list_memory_files and shorten an over-long name; otherwise ask the vault's owner to fix its permissions

  • "cannot write memory file …: that path is not a file" — a folder already has this name; choose another file name

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "cannot create memory folder …" — a file sits where the memory folder belongs; ask the vault's owner to move it

  • "memory file …: the note would open with a properties block …" — saving would turn the file's opening lines into a properties block the server can't keep; the error says how to avoid it

Returns: "Added entry to About Me/.md → ## ", plus a note to fill in the placeholder scope callout when the file is new; an exact duplicate returns "Entry already exists in About Me/.md → ## — nothing was written."

vault_delete_memoryA

Delete a single dated entry from a memory file in About Me/.

Example: vault_delete_memory({ file: "Opinions", section: "Tools and workflows (newest first)", date: "2026-05-01", entry: "Prefer X over Y" }) Example: vault_delete_memory({ file: "Routines", section: "Daily/weekly rhythm (newest first)", date: "2026-03-10", entry: "Gym Tue/Thu at 7am" }) — prune an expired entry from an entry-policy: living file

When to use: Removing an entry that was wrong when it was written. Memory files are append-only by default, so do NOT delete to reflect a change: append the new state via vault_update_memory instead. The exception is a file whose properties declare entry-policy: living (check via vault_list_memory_files) — a current-state file where deleting an expired entry is the intended maintenance. Call vault_get_memory(file, section) first to see exact entry text for matching. Prefer vault_delete_note for deleting whole notes; vault_delete_note refuses protected paths.

Parameters:

  • date + entry together pick the bullet line within the given section; identical copies make the match ambiguous. The entry text matches the bullet's first line only, and the delete also removes the lines that continue that entry.

  • section scopes the match — an identical entry under a different heading is not found.

Errors:

  • "memory file not found" — call vault_list_memory_files to discover valid names.

  • "memory file must not start with a dot" / "… must be a bare name without path separators" / "… must be named without .md" — each message names the part of the name to drop

  • "date must be YYYY-MM-DD" — pass the entry's date exactly as the file shows it, zero-padded.

  • "section not found: …" — no H2 heading matches; the message lists the file's sections

  • "no entry matching …" — verify the exact text with vault_get_memory(file, section).

  • "ambiguous: N entries match …" — remove the extra copy with vault_delete_span (first_match: true) or a manual edit, then retry.

  • "refusing memory write: … would shrink content" — the delete would remove more than half the file; re-read it with vault_get_memory, and remove an entry that really is that large with vault_delete_span or a manual edit.

  • "cannot read memory file …" — the file can't be read: check file against vault_list_memory_files and shorten an over-long name; otherwise ask the vault's owner to fix its permissions

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "memory file …: the note would open with a properties block …" — saving would turn the file's opening lines into a properties block the server can't keep; the error says how to avoid it

Returns: "Deleted entry from About Me/.md → ## ".

vault_get_daily_noteA

Read a daily note by date, using the vault's configured Daily Notes folder and filename date format.

Example: vault_get_daily_note({ date: "2026-05-13" }) Example: vault_get_daily_note({}) — returns today's daily note

When to use: When you need today's or a specific date's daily note. To append content to a daily note section, use the returned path with vault_patch_note. For recent vault activity, vault_recent_notes lists the most recently changed notes; it has no date filter.

Parameters:

  • date: always YYYY-MM-DD, whatever filename format the vault uses; past and future dates are both valid.

Errors:

  • "invalid date …" / "… is not a calendar date" — pass a real date in YYYY-MM-DD form (2026-02-30 is refused)

  • "daily note format contains unsupported token(s): ..." — the configured date format contains tokens the server cannot reproduce; ask the vault's owner to change the format in Obsidian, or the server's operator to set DAILY_NOTES_FORMAT (the message names the tokens)

  • "path traversal blocked" / "hidden path blocked" / "absolute path blocked" — the configured daily notes folder or format leads outside the vault or into a hidden folder; ask the vault's owner to change it in Obsidian, or the server's operator to set DAILY_NOTES_FOLDER and DAILY_NOTES_FORMAT

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

Returns: JSON with path (string — resolved vault-relative path), content (string|null — the note's full text, properties block included, or null when the note doesn't exist), and exists (boolean). When exists is false, create the note with vault_write_note using the returned path.

vault_list_tasksA

List checkbox tasks across the whole vault with structured filters — the Tasks-plugin data model over MCP. Both task metadata formats are indexed: emoji signifiers (📅 due, ⏫ priority, 🔁 recurrence, ...) and Dataview inline fields ([due:: 2026-07-04], [priority:: high], ...). Task lines inside fenced code blocks and %% %% comment blocks are not indexed. Checkboxes typed NON_TASK in the Tasks plugin's status settings are also excluded.

Example: vault_list_tasks({ due: { before: "2026-07-04" } }) — overdue triage; the default status (not_done) and sort (due ascending) make this the "what's overdue?" call Example: vault_list_tasks({ path: "Code Projects/vault-cortex/TASKS.md", heading: ["Active", "Up Next", "Waiting On"], sort_by: "position" }) — actionable Kanban lanes in board order Example: vault_list_tasks({ folder: "Code Projects/vault-cortex" }) — all open tasks across a project tree (TASKS.md + task-notes/ subdirectories) Example: vault_list_tasks({ status: "done", done: { after: "2026-06-26" } }) — what got completed this week

When to use: Reading task status and order on one board (path + sort_by: "position"; add status: "all" to include done and cancelled cards). Also any vault-wide task triage question — "what's overdue?", "what's open per project?", "what did I finish this week?" — in one call instead of per-board reads. Prefer vault_read_note (heading mode) only when you need a lane's verbatim Markdown or a task's state right after a write. Prefer vault_search for full-text queries over note content.

Behavior: A file change takes a few seconds to appear here.

Parameters:

  • status: "not_done" and "all" expand in arrays — ["not_done", "done"] matches todo + in_progress + done.

  • due / scheduled / start / done / created / cancelled: a date filter only matches tasks that HAVE that date.

  • folder: a whole folder, subfolders included ("Projects" covers "Projects/Archive" but not "ProjectsOld/"), ignoring ASCII letter case.

  • sort_by: a task with none of a date sort's dates comes last; "priority" ascending puts highest first, and tasks with no priority sort between medium and low.

Errors:

  • "invalid due.before date: …" (any date filter's bound) — the bound isn't in YYYY-MM-DD form; use that form

  • "… is not a calendar date" — a date filter's bound has the YYYY-MM-DD form but names a day that doesn't exist, such as 2026-02-30; pass a real date

  • "path must end in …" — add the .md extension

  • "tag must not be empty …" — tag is "#" alone; pass the tag name

  • No matches returns { total: 0, tasks: [] }, not an error

  • "[SqliteError]: …" / "[IndexIntegrityError]: …" — the index failed, not the call; the message says what to do

Returns: JSON { total, tasks }. Every task carries path, line, status, status_char (the checkbox character, such as "x" or "/"), description, folder (the task note's parent folder), depth (0 for top-level, 1+ for sub-tasks), is_kanban_task (true when the task's note has kanban-plugin in its properties), depends_on, and tags (the arrays are [] when empty). Every other field appears only when the task has it: heading (nearest heading above the task), created/scheduled/start/due/done/cancelled dates, priority, recurrence, on_completion, task_id, block_id, parent_block_id (sub-tasks whose parent carries a ^block-id), done_lanes (Kanban boards only: the headings of the lanes whose first line is Complete), and subtask_progress ({ done, total } over the task's DIRECT checklist children; done counts status "done" only, a cancelled child counts toward total, and the counts ignore the query's filters).

vault_create_taskA

Create a correctly-formatted task in one call — description, target heading, dates, priority, block_id, and optional checklist sub-items. The task is always created as todo, with its created date (➕) set to today; set in_progress later with vault_update_task. Override format only at the user's request. A retried call with no block_id (a Kanban sub-task) creates it again, so after a timeout or lost response, re-read the note before retrying.

Example: vault_create_task({ path: "TASKS.md", description: "Fix login bug", block_id: "fix-login", heading: "Active", priority: "high", due: "2026-09-15" }) — a card in a Kanban board's Active lane Example: vault_create_task({ path: "TASKS.md", description: "Sub-bug", parent_block_id: "fix-login", due: "2026-09-01" }) — full sub-task under fix-login Example: vault_create_task({ path: "Projects/plan.md", description: "Quick fix", block_id: "quick-fix", parent_line: 42 }) — sub-task in a regular note under a parent identified by line number Example: vault_create_task({ path: "TASKS.md", description: "Write release notes", block_id: "release-notes", heading: "Active", position: 3 }) — insert as the 3rd card in the lane

When to use: Creating a new task card on a board or in a note. For lightweight checklist items under an existing card (no metadata), use vault_update_task's add_subtasks param instead.

Parameters:

  • heading is required on Kanban boards (notes with a kanban-plugin property) unless a parent is given.

  • parent_block_id / parent_line: pass at most one. Either is mutually exclusive with heading — a sub-task lives wherever its parent lives.

  • block_id / subtasks: block_id is required, except on a Kanban sub-task, where it is refused, as is a subtasks item ending in a ^block-id on a board.

  • position: ignored without a heading, and under a parent, where the sub-task goes after the parent's last sub-item.

  • recurrence: a rule ending "when done" bases the next occurrence on the completion day; a task with no dates recurs as a dateless copy.

Errors: (messages use camelCase names, e.g. blockId for block_id)

  • "note not found" — path does not exist; check the path with vault_list_notes

  • "path must end in …" — add the .md extension

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "heading required for Kanban boards" — pass heading with the target lane

  • "heading "X" not found; available: ..." — the message lists the note's headings

  • "cannot place at position P under "X" — the heading appears N times" — integer position on a note with duplicate heading names; use "top" or "bottom" (they place under the first match), or rename one section to make it unique

  • "parent task not found" — parent_block_id or parent_line doesn't resolve to a task outside a fenced code block or %% %% comment; re-read the parent's block_id or line with vault_list_tasks

  • "parent task ambiguous" — more than one task line ends in that ^id (the message lists their line numbers); pass parent_line instead

  • "checkbox "[c]" is a NON_TASK status" / "no checkbox symbol for status ..." — in the Tasks plugin's status settings, the parent's checkbox character (c) is typed NON_TASK, or todo has no symbol and the default [ ] character has another type; the user changes that character's type or adds a todo symbol there, then restarts the server

  • "parentBlockId and parentLine are mutually exclusive" / "parent and heading are mutually exclusive" — drop one

  • "blockId ... already exists in this note" / "blockId ... contains invalid characters" — pick a block_id no other line in the note ends in; the invalid-characters message names the allowed characters

  • "blockId is required" — pass block_id

  • "blockId is not allowed on a sub-task on a Kanban board" / "subtask ... ends in a block ID" — the message names what to drop

  • "description is empty" / "subtasks cannot contain an empty item" / "description must be a single line" / "subtasks items must be a single line" — pass visible text on a single line

  • "taskId ... contains invalid characters" / "dependsOn entry ... contains invalid characters" — the message names the allowed characters

  • "unrecognized recurrence rule ..." — the message gives example rules

  • "invalid date: …" — the message names the expected format

  • "concurrent write in progress" — another write to this note is in flight; retry

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "properties block …" — the note's properties block can't be read, or a rewrite would lose it; the error says how to repair it

  • "the note would open with a properties block …" — a note without properties would start with --- lines around text that can't be properties; the error says how to avoid it

Obsidian syntax: When description or subtasks text ends in a field the plugin's parser recognizes (an emoji field like "🔁 every week", or a Dataview [key:: value] field), with only other recognized fields after it, that text is read back as metadata. The write still succeeds; reword the text to clear the advisories the result carries.

Returns: JSON { path, line, description, block_id, heading, subtasks, changes, advisories } — line is the new task's 1-based line number in the file; block_id is omitted when the task has none (line is then its handle); heading is the nearest heading above the new task (omitted when the note has none); subtasks lists each checklist item written as { line, description } (omitted when none); changes lists every field written as "field: (none) → value", and checklist items as "subtasks: 0 → N"; advisories (omitted when the line round-trips clean) lists one sentence per place the stored line parses back differently than submitted — see Obsidian syntax above.

vault_update_taskA

Update a task's status, priority, description, dates, recurrence, completion action, dependencies, block_id, checklist items, heading, or position within a lane, in any combination; every field passed is written in a single edit. A repeated call with add_subtasks adds the items again, and a repeated "done" or "cancelled" moves its date stamp to the retry day, so after a timeout or lost response, re-read the task before retrying.

Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", status: "done" }) — complete a task; on a Kanban board, auto-moves to the done lane Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", recurrence: "every week" }) — make a task recurring Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", heading: "Done" }) — move a task to a different heading (the checkbox is unchanged) Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", description: "Updated task name", due: "2026-10-01", scheduled: null }) — change the description, set one date, and clear another Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", status: "in_progress", add_subtasks: ["Design", "Implement", "Test"] }) — start working and add checklist stages Example: vault_update_task({ path: "TASKS.md", line: 42, assign_block_id: "my-task" }) — add a block_id to a task that lacks one

When to use: Any change to an existing task. Use vault_list_tasks first to get identification fields (path + block_id or line). For creating a new task, use vault_create_task instead.

Parameters:

  • Exactly one of block_id or line is required to identify the task. A Kanban sub-task this server created has no block_id, so re-read its line with vault_list_tasks just before an update by line. After a write, wait a few seconds for the listing to catch up.

  • At least one change is required. Clearing is always explicit null — omitting a field leaves it untouched.

  • status side effects: Kanban: "done" moves the card and everything nested under it to the done lane (the lane whose first line is Complete, else the heading "Done", exact case), leaving nested checkboxes as they are; a sub-task stays under its parent. Recurring (🔁), on "done" only: spawns the next occurrence, dates advanced per the rule, directly above the completed task, which keeps its nested lines. When the Tasks plugin's settings put the next recurrence on the line below, the spawn goes on the line directly below the task instead, so the task's nested lines then sit under the spawn. The spawn stays in the source lane with no block_id, 🆔, or ⛔ — follow up with assign_block_id on next_occurrence.line, except on an indented line of a Kanban board. Completing by line is NOT idempotent for recurring tasks (the spawn occupies the old line); prefer block_id. Delete (🏁 delete / [onCompletion:: delete]), on "done": removes the task line and its children instead of moving to done. With 🔁 + 🏁, the next occurrence is still created, and the completed task is removed.

  • recurrence: a rule ending "when done" bases the next occurrence on the completion day. When set together with status "done", the new rule governs the next occurrence. Setting recurrence to null while completing removes the rule and completes without spawning.

  • on_completion: passed together with status, the submitted value governs the delete decision — "keep" while completing a "delete" task prevents the deletion.

  • position: applies to a heading move or an auto-done-lane move; otherwise it reorders the task within its current lane. Ignored when the task is deleted on completion.

Errors: (messages use camelCase names, e.g. assignBlockId for assign_block_id)

  • "note not found" — path does not exist; check the path with vault_list_notes

  • "path must end in …" — add the .md extension

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "exactly one of blockId or line is required" / "blockId and line are mutually exclusive" — the message states the rule

  • "blockId ... not found" / "blockId ... is inside a fenced code block or comment" — no task line outside a fenced code block or %% %% comment ends with that ^id; check the id with vault_list_tasks, or target by line

  • "blockId ... matches N task lines" — the message lists the lines; target the intended task by line, then repair: on a regular note, give all but one line a new id with assign_block_id; on a Kanban board, where the plugin copies a sub-task's id onto its card, clear the sub-task's id (assign_block_id: null, by line) and give the card the id its links use, or a new one

  • "no task at line N" / "line N is inside a fenced code block or comment" — the line holds no task checkbox, or sits inside a fenced code block or %% %% comment; re-read line numbers with vault_list_tasks, or target by block_id

  • "checkbox "[c]" is a NON_TASK status" / "no checkbox symbol for status ..." — in the Tasks plugin's status settings, the task's checkbox character (c) is typed NON_TASK, or the named status has no symbol and its default character has another type; the user changes that character's type or adds the symbol there, then restarts the server

  • "at least one mutation" — the message lists the fields that count

  • "cannot move a sub-task to a heading" / "cannot reposition a sub-task" — a heading or position on a task nested under another task (depth > 0 in vault_list_tasks); sub-tasks move with their parent, so move or reposition the parent instead

  • "cannot reorder a task that sits above the first heading" — position without a heading on a task before the first section heading; the message names the fix

  • "cannot reorder within "X" — the heading appears N times" / "cannot place at position P under "X" — the heading appears N times" — the heading name is duplicated in the note: the task's own heading for a reorder in place, the one in heading for a move; for a cross-lane move use "top" or "bottom" (they place under the first match), or rename one section to make it unique

  • "heading "X" not found; available: ..." — the message lists the note's headings

  • "multiple done lanes detected" / "no done lane detected" — status "done" on a Kanban board with more than one lane whose first line is Complete, or with none and no "Done" heading; pass heading to pick the lane

  • "blockId ... already exists" / "blockId ... contains invalid characters" — assign_block_id must be unique in the note and match [a-zA-Z0-9-]+

  • "assignBlockId is not allowed" / "description ends in a block ID" / "subtask ... ends in a block ID" — a new ^id on an indented line of a Kanban board; leave the line without an id and target it by line, and drop any trailing ^id from the text

  • "invalid date: …" — the message names the expected format

  • "description cannot be empty" / "addSubtasks cannot contain an empty item" / "description must be a single line" / "addSubtasks items must be a single line" — pass visible text on a single line

  • "taskId ... contains invalid characters" / "dependsOn entry ... contains invalid characters" — the message names the allowed characters

  • "unrecognized recurrence rule ..." — the message gives example rules

  • "concurrent write in progress" — another write to this note is in flight; retry

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "properties block …" — the note's properties block can't be read, or a rewrite would lose it; the error says how to repair it

  • "the note would open with a properties block …" — a note without properties would start with --- lines around text that can't be properties; the error says how to avoid it

Obsidian syntax: When description or add_subtasks text ends in a field the plugin's parser recognizes (an emoji field like "🔁 every week", or a Dataview [key:: value] field), with only other recognized fields after it, that text is read back as metadata. The write still succeeds; reword the text to clear the advisories the result carries. The ✅/❌ dates a status change adds or removes produce no advisories.

Returns: JSON { path, line, description, block_id, heading, subtasks, next_occurrence, changes, advisories, on_completion_applied }.

  • line is the task's final 1-based line number in the file; after an on_completion delete, the line the task occupied before removal.

  • description is the current text, and block_id and heading reflect the task after the update; block_id is omitted when the task has none, heading when the task sits above the first heading.

  • subtasks lists each checklist item add_subtasks wrote as { line, description } (omitted when none were added); checklist items are written without a block_id, so line is the handle for a follow-up update.

  • next_occurrence is present only when a completion spawned a recurring task's next occurrence: { line, description, due?, scheduled?, start? } with only the dates the occurrence has.

  • changes lists every field applied as "field: before → after", with "(none)" for an absent value; an add_subtasks change is listed under "subtasks", a spawn under "next_occurrence", and a 🏁 delete under "on_completion".

  • advisories (omitted when there are none) lists one sentence for each: a stored line that parses back differently than submitted (see Obsidian syntax above), a duplicate tag removed from the line, or a completed recurring task whose rule yields no next occurrence (unreadable rule text, or a finite rule with no occurrences left).

  • on_completion_applied is "delete", present only when the effective on_completion was delete (already on the task or set in the same call) and the task was transitioned to done.

vault_read_fileA

Read a non-markdown vault file in its most useful form per type — the read-side companion to vault_read_note for everything that isn't a note.

Example: vault_read_file({ path: "attachments/diagram.png" }) — the image itself, shrunk when too large Example: vault_read_file({ path: "Boards/Roadmap.canvas" }) — a readable outline of the canvas Example: vault_read_file({ path: "exports/data.json" }) — the file content as text Example: vault_read_file({ path: "exports/big.csv", limit: 500 }) — the first 500 lines Example: vault_read_file({ path: "papers/research.pdf" }) — structured text with title, headings, and links

When to use: whenever a note references a file you need to actually see or read — an embedded diagram, a linked canvas, data file, or PDF. Find the files a note links to (with byte sizes) via vault_get_outgoing_links. Browse a folder's files via vault_list_files. vault_search also indexes canvas, PDF, and text-format content, but not images or other files. For .md notes use vault_read_note. To check a large file's line count before reading it whole, request start_line: 1 with limit: 1 — one line plus the total.

Parameters:

  • raw: true switches a canvas or PDF to its other form. A canvas returns its exact JSON source (geometry, ids, colors) instead of the outline. A PDF returns each page rendered as an image instead of extracted text, showing layout, diagrams, tables, and formatting; only the first 5 pages are rendered, and later pages are readable only as text, without raw.

  • start_line and limit page any text result as a line window. The text output cap (a fixed 100 KiB) applies to each window, so one very long line can still overflow it; paging never gets around the file-size cap.

Behavior: A paged window comes back with \n line endings and no trailing newline, preceded by a metadata line stating the window, the total line count, and where to continue ("data.csv — lines 51–100 of 400 (continue with start_line: 101)"); an unpaged read of a text format returns the file byte-exact.

Errors:

  • "not a file: … is a markdown note" — the path ends in .md; read notes with vault_read_note

  • "file not found" — no file exists at that path (a folder is not a file); discover valid paths via vault_list_files

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part

  • "file too large" — the file exceeds the file-size cap (MAX_FILE_BYTES, default 50 MiB), which the server's operator can raise

  • "text output too large" — a text file, canvas, or PDF renders past the text output cap; page it with start_line and limit, or reduce limit when a single window overflows

  • "start line past the end" — start_line exceeds the file's line count; the error states the total, so retry with a smaller start_line

  • "line range is not available" (start_line or limit on an image, or on a PDF read with raw: true) / "raw source is not available for images" (raw on an image) — drop that input

  • "not valid UTF-8" — the file's bytes aren't UTF-8 text; ask for a UTF-8 copy

  • "invalid .canvas JSON" — the canvas file is empty or not valid JSON; set raw: true to read its source as text

  • "PDF has no extractable text" — the PDF contains no text (scanned or image-only); the error states the page count. Set raw: true to render pages as images instead

  • "PDF is password-protected" — read an unprotected copy instead

  • "PDF is damaged or not a PDF" / "could not decode image" — the PDF does not parse, or the image is empty, damaged, not an image despite its extension, or over about 268 million pixels; any other PDF or image error comes back in the reading library's own words. Replace or re-export the file

  • "PDF page rendering failed" — read it without raw: true to get its text instead; if that read also finds no text, this tool can't read the PDF

  • "image cannot be fitted into …" — the image is over the image output budget even after shrinking; ask the server's operator to raise MAX_IMAGE_OUTPUT_BYTES

  • "ENAMETOOLONG: …" or another filesystem error — the message says what to do

  • "file changed while reading" — retry the read

  • "PDF reader could not start" — the server's PDF reader failed to load; retry once, then ask the server's operator to restart the server

  • "unsupported file type …" — the type (audio, archives, …) is not readable; the error names the readable types plus the file's existence and size

Returns: by file type:

  • Images (.png/.jpg/.jpeg/.gif/.webp): an image content block plus a one-line metadata text block. The image is downscaled and recompressed when it exceeds the image output budget (49152 bytes) or has a longer side over 1568 pixels; an animated GIF is reduced to its first frame when recompressed.

  • Canvas (.canvas): one text block holding a readable markdown outline — groups, node content in reading order, and a connections list with edge labels.

  • PDFs (.pdf): one text block of structured text with document metadata — title, page count, heading hierarchy, code blocks and inline code, page separators, and a links footer; with raw: true, a metadata text block followed by alternating image and text blocks (one pair per page).

  • Text formats (.svg/.json/.txt/.csv/.xml/.log/.yaml/.yml/.base): one text block holding the file content verbatim.

vault_list_filesA

List non-markdown files in the vault or a folder — images, canvases, PDFs, data files — with per-file byte sizes and per-extension counts.

Example: vault_list_files({}) — non-markdown files across the whole vault Example: vault_list_files({ folder: "attachments" }) Example: vault_list_files({ extensions: [".png", ".jpg"], limit: 20 })

When to use: discovering what files exist before reading them with vault_read_file. vault_list_notes and vault_search_by_folder cover only markdown notes, and beyond notes vault_search indexes only canvas, PDF, and text-format files, so this is the discovery surface for everything else. For the files one specific note links to, prefer vault_get_outgoing_links.

Behavior: Use the folder's exact letter case; a different case can find nothing. Hidden (dot-prefixed) files and folders, .obsidian/ among them, never appear.

Errors:

  • A visible folder containing no files — or one that doesn't exist, or a path that names a file — returns an empty listing, not an error.

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — the folder is absolute, escapes the vault, names the vault root itself (e.g. "."), or is hidden like ".obsidian"; use a vault-relative folder outside hidden folders, and omit folder to list the whole vault.

  • "ENAMETOOLONG: …" or another filesystem error — the message names the folder it could not read, and what to do; until then, pass a folder outside it.

Returns: JSON with files (array of { path, extension, bytes }, sorted by path; extension is "(none)" for a file without one), extension_counts (counts keyed by extension, e.g. { ".png": 3 }, over the full filtered set), total (full filtered count), and truncated (true when total exceeds limit).

Readable types: vault_read_file reads only images (.png/.jpg/.jpeg/.gif/.webp), .canvas, .pdf, and the text formats .svg/.json/.txt/.csv/.xml/.log/.yaml/.yml/.base.

Prompts

Interactive templates invoked by user choice

NameDescription
vault-orientationSurvey this vault's structure and health — stats, folders, tags, properties (with adoption rates), orphans, recent notes, and the About Me/ memory layer.
memory-reviewReflect on the About Me/ memory layer — review its structure and scopes, read dated entries as a timeline, surface scope-fit issues and coverage gaps, and propose append-only updates. Never prunes entries for being old, except expired entries in files marked entry-policy: living.
daily-reviewReconcile a day — daily note, vault-wide task status (due/overdue, scheduled), modified notes, and links — surface what happened, what's open, and durable facts worth saving to About Me/ memory.

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

A4.1/5.0

Scored across 33 tools

Disambiguation3/5

Most tools are documented with explicit cross-references and 'prefer X' guidance, but the set contains several closely overlapping note-edit tools (vault_patch_note, vault_replace_in_note, vault_replace_span, vault_insert_at_anchor, vault_delete_span, vault_write_note) whose boundaries an agent must reason through carefully. The search/discovery family also overlaps heavily: vault_search, vault_search_by_tag, vault_search_by_folder, vault_search_by_property, vault_list_notes, vault_recent_notes, and vault_list_files each have justified niches, but choosing among them is not effortless.

Naming Consistency4/5

Almost all tools use a predictable vault_verb_noun snake_case pattern, with clear names like vault_list_notes, vault_read_note, vault_create_task, and vault_update_properties. Minor deviations such as vault_recent_notes, vault_memory_recall, and the search_by_* family are still readable and consistent enough not to cause confusion.

Tool Count2/5

At 33 tools, the surface is large even for a full-featured Obsidian server spanning notes, search, links, files, memory, tasks, and daily notes. While most tools have domain justification, the total is heavy and includes multiple near-duplicate edit and discovery tools that make the set harder to navigate than necessary.

Completeness4/5

Core vault workflows are well covered: note CRUD, section edits, property updates, search, tags, backlinks, files, memory, tasks, and daily notes. Minor gaps remain around non-markdown file write/delete, folder management, and broader attachment operations, but agents can work around most of these.

Maintenance

ActivityActive
ResponsivenessUsually handled