Skip to main content
Glama
aliasunder

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.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
completions
{}
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 — so large notes don't blow the token budget. Set at most one of properties_only, outline, or heading.

Example: vault_read_note({ path: "Projects/vault-cortex.md" }) Example: vault_read_note({ path: "Projects/vault-cortex.md", properties_only: true }) 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 }) // disambiguate when several "Done" headings exist Example: vault_read_note({ path: "TASKS.md", heading: "Done", start_line: 1, limit: 20 }) // first 20 lines of an oversized section

When to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: "..." to read just the one section you need — both far cheaper than pulling the whole file. Use properties_only: true when you only need properties. For an oversized note or section, page it with start_line and limit to read a window at a time. To check a note's or section's line count, request start_line: 1 with limit: 1 — one line plus the total. Prefer vault_search when you don't know the path. 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.

Section boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included.

Errors:

  • "note not found" / "ENAMETOOLONG: …" — no note exists at this path, or the path (or a name in it) is longer than the filesystem allows; verify it with vault_list_notes

  • "heading not found" — no heading matches the text; error lists available headings

  • "ambiguous heading" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level

  • "outline, heading, and properties_only are mutually exclusive" — only one mode per call

  • "heading_level requires a heading" — heading_level only disambiguates 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/limit only work on text renditions (full read or heading section)

  • "start line past the end" — start_line exceeds the rendition's line count; error states the total

  • 'path must end in ".md"' — 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" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it

  • "properties block is not valid YAML …" — properties_only, outline and heading need readable YAML, while a full read returns the note as it is; the error says how to repair it

  • "EACCES: …" or another filesystem error code — the note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it

Returns: Raw markdown string (default); JSON object of properties, {} when the note has none (properties_only); JSON outline object, shaped as the outline parameter describes (outline); raw markdown of the section, heading line included (heading). When start_line or limit is given, the result is preceded by a window-metadata text block ("path — lines 1–20 of 250 (continue with start_line: 21)"). Unpaged full reads return the file's exact bytes; heading sections and paged reads use LF line endings.

Outline: bytes at the root is the whole file's on-disk size and modified is its filesystem modification time; each heading's bytes is the exact UTF-8 byte length of the text that heading mode returns for that section. Empty headings ("##" with no text) appear with text: "" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.

vault_list_notesA

List .md file paths in the vault, 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/". This tool reads the filesystem rather than the search index, so use the folder's exact letter case, as other results show it; on a case-sensitive filesystem a different case finds 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 the folder's top-level notes and "**/.md" every note under it. Returned paths are always vault-relative.

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, matching Obsidian; symlinked notes are included.

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.

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

vault_write_noteA

Create a markdown note. Errors if a note already exists at the path unless overwrite is set. Body replaces the entire note content: existing content will be lost unless you include it in body, so do not use this tool for surgical edits to large files. 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. Set overwrite: true only when you intend to replace an existing note's body. 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, or use vault_patch_note or vault_replace_in_note for partial edits

  • "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" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it

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

  • "body contains a control character" — body includes a non-printable control byte; remove it before writing

  • "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 the server cannot keep …" — the note would get no properties (none passed or kept from an overwritten note, or every key set to null), and the body opens with --- lines around text that is invalid YAML, a list, a single value, or a YAML tag, which would become its properties block; pass at least one property, or start the body without --- lines

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. Frontmatter values are preserved; YAML formatting may be normalized to block style on first edit.

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" })

When to use: Modifying part of an existing note without overwriting the entire body. Prefer vault_write_note for creating new notes, or full rewrites (with overwrite: true). Prefer vault_replace_in_note for in-place text changes (typos, renaming) that stay in the same location. Prefer vault_insert_at_anchor for inserting next to a specific line inside a section. Prefer vault_replace_span for replacing a run of lines inside a section. Prefer vault_delete_span for removing lines. Prefer vault_update_properties for changing frontmatter properties. Prefer vault_create_task for adding a task. Prefer vault_update_task for completing or moving a task in one write.

Operations:

  • append: add content at end of section (or end of file if no heading)

  • prepend: add content after heading line (or at the top of the body, below frontmatter, if no heading — how you add a leading callout). To start a new section above the note's current first heading, use insert_before on that heading, not a no-heading prepend.

  • replace: replace section body (requires heading)

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

Heading-targeted ops keep the matched heading and write content verbatim. No separator is added around the content — end it with a newline to leave a blank line after the inserted block.

Limitation: A no-heading prepend inserts at body line 0. If the note has content above its first heading and your content starts with a heading, the pre-existing content becomes the new section's body. The write still succeeds.

Section boundaries: a section spans from its heading to the next heading of the same or higher level (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.

Editing 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" — no heading matches the text; error lists available headings

  • "ambiguous heading" — multiple headings match; use heading_level to disambiguate, or use vault_replace_in_note to target by text content when headings share the same level

  • "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" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it

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

  • "content contains a control character" — content includes a non-printable control byte; remove it before writing

  • "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 …" — the edit would leave --- lines at the top of a note with no properties, around text that can't be kept as 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. Inserting heading-level content (## New Section) changes the note's structure — future heading-targeted ops may resolve differently. 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 the call that would have avoided it.

vault_replace_in_noteA

Find and replace text in a markdown note's body. Matches exact text (case-sensitive). Properties are preserved; YAML formatting may be normalized to block style on first edit. Operates on the body only — properties must be edited via vault_update_properties or vault_write_note's properties parameter.

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: "" }) — removes the whole line, line break included.

When to use: Targeted text changes within a single location — fixing typos, updating values, renaming terms, or removing a short line (new_text=""). Replaces text in place; does not move content across sections. 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. No regex.

  • new_text: non-empty new_text replaces the match exactly. A deletion (new_text="") that leaves an empty line where the match was joins the empty lines above and below it into one gap that keeps the larger of the two counts (only at the end of the note, the count above drops by one); where matches empty their lines, the gap keeps at least one empty line unless it ends the note, so include the line break in old_text to remove the line. Any other deletion, such as text inside a line or a line break that joins two lines, is written exactly as asked. Empty lines outside the joined gaps never change. A line holding only spaces or tabs counts as text, not as an empty line.

  • replace_all_occurrences: replacing only the first match is a safety default for when old_text appears in multiple places. Set true for deliberate bulk renames or term replacements.

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" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it

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

  • "new_text contains a control character" — new_text includes a non-printable control byte; remove it before writing

  • "ENAMETOOLONG: …" — the path, or a name in it, is longer than the filesystem allows; check the path. If the quoted name ends in ".tmp", the note exists but its name is too long to save; rename it at least 41 bytes shorter

  • "EACCES: …" or another filesystem error code — the note's file can't be read or written (permissions, a full or read-only disk); ask the vault's owner to fix it

  • "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 …" — the edit would leave --- lines at the top of a note with no properties, around text that can't be kept as 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 " — N is the number of matches replaced.

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. Each anchor locates a full line — the entire line is selected, not just the matching substring. Case-sensitive matching. Properties are preserved; YAML formatting may be normalized to block style on first edit. Operates on the body only.

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. Pick a short, unique fragment of the first line for start_anchor and, for a multi-line block, the last line for end_anchor. Prefer vault_replace_in_note for small in-place edits (this tool only deletes). To replace a block, use vault_replace_span (one atomic step).

Parameters:

  • start_anchor + end_anchor define a line range, not a text range (never cuts mid-line). Omit end_anchor for a single-line delete. The empty lines above and below the removed lines join into one gap that keeps the larger of the two counts (only at the end of the note, the count above drops by one); no other empty line in the note changes. A line holding only spaces or tabs counts as text, not as an empty line.

  • end_anchor is searched at or after the start line, so the span can never run backward; it must be unique among those lines. If both 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 (```): every fragment of the closing fence also appears in the opening fence, so 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: "".

  • first_match applies to both anchors independently — when an anchor matches multiple lines, takes the first instead of erroring.

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 line contains the fragment (for end_anchor, none at or after the start line); 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" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it

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

  • "ENAMETOOLONG: …" — the path, or a name in it, is longer than the filesystem allows; check the path. If the quoted name ends in ".tmp", the note exists but its name is too long to save; rename it at least 41 bytes shorter

  • "EACCES: …" or another filesystem error code — the note's file can't be read or written (permissions, a full or read-only disk); ask the vault's owner to fix it

  • "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 …" — the edit would leave --- lines at the top of a note with no properties, around text that can't be kept as properties; the error says how to avoid it

Returns: Confirmation with the number of lines the span covered and a preview of them, cut at 80 characters.

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. Each anchor locates a full line — the entire line is selected, not just the matching substring. Case-sensitive matching. Properties are preserved; YAML formatting may be normalized to block style on first edit. Operates on the body only.

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. Pick a short, unique fragment of the first line for start_anchor and, for a multi-line block, the last line for end_anchor. Prefer vault_replace_in_note for small in-place text changes (typos, renaming). Prefer vault_delete_span when removing without replacement.

Parameters:

  • end_anchor is searched at or after the start line, so the span can never run backward; it must be unique among those lines. If both 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 (```): every fragment of the closing fence also appears in the opening fence, so 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.

  • content: empty lines at its start and end join the empty lines around the replaced lines, and each joined gap keeps the larger of the two counts (only at the end of the note, the count above drops by one). So content can widen a gap but not narrow it: a trailing newline leaves at least one empty line after the new block unless the block ends the note. Content made only of empty lines joins both sides into one gap. Empty lines inside content are written as given, and no other empty line in the note changes. A line holding only spaces or tabs counts as text, not as an empty line.

  • first_match applies to both anchors independently.

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 line contains the fragment (for end_anchor, none at or after the start line); 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" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it

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

  • "content contains a control character" — content includes a non-printable control byte; remove it before writing

  • "ENAMETOOLONG: …" — the path, or a name in it, is longer than the filesystem allows; check the path. If the quoted name ends in ".tmp", the note exists but its name is too long to save; rename it at least 41 bytes shorter

  • "EACCES: …" or another filesystem error code — the note's file can't be read or written (permissions, a full or read-only disk); ask the vault's owner to fix it

  • "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 …" — the edit would leave --- lines at the top of a note with no properties, around text that can't be kept as 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 lines with lines in " — N counts the lines the span covered, M the line breaks in content plus one.

vault_insert_at_anchorA

Insert content as whole lines before or after a specific line identified by a short anchor substring. Case-sensitive matching; an anchor matching more than one line is an error unless first_match is set. Same anchor resolution as vault_delete_span. Properties are preserved; YAML formatting may be normalized to block style on first edit. Operates on the body only.

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:

  • anchor locates a full line — the insert never splits a line.

  • 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" — fragment not on any line; 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" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it

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

  • "content contains a control character" — content includes a non-printable control byte; remove it before writing

  • "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 …" — the edit would leave --- lines at the top of a note with no properties, around text that can't be kept as 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 lines <before|after> anchor in " — N counts the lines content supplied.

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 (trashOption in .obsidian/app.json) decides the outcome:

    • "Move to system trash" (system, also what an absent setting means) moves the note to .trash/, since the server has no system trash. Notes this server moved there are deleted after its retention period (TRASH_RETENTION_DAYS: 30 days by default, never when set to none); notes Obsidian itself trashed are never touched.

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

    • "Permanently delete" (none) removes the note for good.

  • The caller can't choose or see the outcome in advance; the returned message says which happened.

  • 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 (read from DAILY_NOTES_FOLDER or .obsidian/daily-notes.json, defaulting to Daily Notes/; not protected when it is the vault root or blank).

Parameters:

  • prune_empty_folders removes each parent folder the delete leaves with zero entries, up to but never including the vault root; a folder holding any file, even a hidden .DS_Store, is kept. Pruning runs after the delete or trash move and is best-effort: a folder that can't be removed never fails the call. Without it, empty folders stay, matching Obsidian.

Errors:

  • "cannot delete protected path" — the path sits under a protected folder; use vault_delete_memory for memory entries

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

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

  • "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 ("Plan 1.md" … "Plan 100.md"); ask the vault owner to clear old copies, then retry

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

  • "cannot delete …" (other than a protected path), "EACCES: …" or another filesystem error code — the note stays put; ask the vault owner to fix the cause (e.g. permissions), then retry

  • "ENAMETOOLONG: …" — the path, or a name in it, is longer than the filesystem allows; check the path

  • "cannot read trash config from .obsidian/app.json" — the file exists but can't be read or parsed, and guessing the setting could let the server's trash cleanup delete a note Obsidian keeps forever; ask the vault owner to repair it, then retry

  • "cannot read daily notes config from .obsidian/daily-notes.json" — the file exists but is unreadable, so the daily notes folder to protect is unknown; ask the vault owner to repair it, or the server 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 frontmatter links (e.g. related:) — 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]], img). 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. Moving a note any other way can silently break its backlinks.

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, updating links and the note's own relative links. 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 cannot be moved: About Me/ and the daily notes folder (read from DAILY_NOTES_FOLDER or .obsidian/daily-notes.json, defaulting to Daily Notes/; not protected when it is the vault root or blank).

Parameters:

  • prune_empty_folders removes each parent folder of old_path that the move leaves with zero entries, up to but never including the vault root; a folder holding any file, even a hidden .DS_Store, is kept. 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. Without it, empty folders stay, matching Obsidian.

Errors:

  • "destination exists: …" — a note already lives at new_path; this tool never overwrites. Pick a free path or delete the existing note first.

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

  • "source and destination are the same path" — old_path and new_path name the same note, so there is nothing to move.

  • "cannot move protected path …" / "cannot move into protected path …" — old_path or new_path sits under a protected folder.

  • "cannot read daily notes config from .obsidian/daily-notes.json" — the file exists but is unreadable, so the daily notes folder to protect is unknown; repair it, or set DAILY_NOTES_FOLDER or PROTECTED_PATHS, then retry.

  • "path must end in …" — both old_path and new_path must end in .md.

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — use vault-relative paths with no hidden (dot-prefixed) file or folder in them (notes cannot move from or into hidden paths, matching Obsidian).

  • "concurrent write in progress" — a write is in flight on the note, the destination, or one of its backlink sources (the move locks all of them as one unit); retry the move.

  • "backlink set did not stabilize" — the vault was modified during the move and new backlink sources kept appearing across retries; nothing was written; retry the move.

  • An ordinary move that fails partway (rare: a permission or disk error) — no data is lost, and the error names what failed and the resulting state. The original is deleted last, after the destination and every backlink are written. If a backlink write failed: new_path exists and old_path is intact, so delete the partial new_path, then re-run the move. If the final delete failed: both paths exist, so delete old_path to finish.

  • A case-only rename that fails partway — the note is renamed in place first. If the rename failed: nothing was written. If a later link write failed: the note already lives at new_path and old_path is gone, so fix the remaining links in place (the error names the note whose update failed) instead of re-running the move.

  • "move aborted: could not read …" / "move aborted: could not rewrite …" — the note, or a note linking to it, could not be read or has a properties block the move cannot keep; nothing was written; fix that note (for a properties block, the error says how), then retry.

  • "move aborted: could not write to …" — new_path could not be created; nothing was written. Saving stages a temp file 41 bytes longer than new_path's name, so a long name fails; retry with one at least 41 bytes shorter. If that fails, the folder or disk can't be written; ask the vault's owner to fix it.

  • "ENAMETOOLONG: …" — old_path or new_path, or a name in it, is longer than the filesystem allows; check the path.

  • "EACCES: …" or another filesystem error code — a note or folder can't be read or written (permissions, a full or read-only disk); nothing was written; ask the vault's owner to fix it.

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. Only the target path is changed.

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 that were edited; the moved note is implied by moved_to), and pruned_empty_folders (count of empty parent folders removed — 0 unless prune_empty_folders was set).

vault_update_propertiesA

Update a note's frontmatter 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 (null writes an empty property, {} removes it). A merge refuses a block it cannot keep — invalid YAML, a list, a single value, or a YAML tag — so replace is how to repair one. The body is kept; a merge only adds a missing final newline.

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 block. 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" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it

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

  • "properties block …" — without replace: 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 …" — the edit would leave --- lines at the top of a note with no properties, around text that can't be kept as 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 all vault notes, ranked by combined keyword and semantic relevance using Reciprocal Rank Fusion (RRF) — combining FTS5 keyword matching with vector similarity. Results are refined by a cross-encoder reranker using position-aware score blending when available. Semantic matching finds notes even when exact keywords differ — "career aspirations" finds notes about "goals" and "targets". Falls back to keyword-only (FTS5 BM25) transparently while embeddings are being built. Combine a text query with structured filters to narrow results by metadata — the "narrow by metadata, search by text" pattern. Unquoted terms use implicit AND with porter stemming; wrap in double quotes for exact phrases; punctuated terms (vault-cortex, deploy/local) are matched as exact adjacent-word phrases automatically.

Filters — all conditions AND-combine with each other and the text query:

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

  • tags: every listed frontmatter tag must match, in any letter case; "project" also matches "project/a"

  • properties: arbitrary frontmatter key-value pairs, supports string/number/boolean (e.g. { status: "active" }). Values compare by exact type — pass a number as a number, not "4". Exception: checkbox values are stored as 1 and 0, so pass true to match 1 and false to match 0. A list property matches when any element equals the value.

  • created / modified: bounds compare whole calendar days, server-local, so on matches anywhere within the day. Notes without a parseable created property never match a created filter

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 Example: vault_search({ query: "how the server watches for file changes" }) — semantic: finds notes about chokidar and file watchers even without those exact terms

When to use: The primary discovery tool for content-based queries, optionally constrained by metadata. Semantic matching bridges vocabulary gaps — try natural-language queries, not just keywords. 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.

Errors:

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

  • Malformed query syntax is sanitized automatically — the tool never throws a query syntax error

  • "invalid created.on date: …" (any created/modified bound) — the bound isn't in YYYY-MM-DD form; use that form

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

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

Returns: JSON with results array (path, title, snippet, score, tags, folder, type, kind, extension, created, modified, bytes), total (results returned, not all matches), search_mode ("hybrid" or "fts"), and reranked (boolean — true when cross-encoder reranking refined the ordering). search_mode indicates which ranking was used — "hybrid" when vector embeddings contributed, "fts" when only keyword matching was available. score reflects combined relevance (higher = more relevant). kind is "note" for markdown notes or "file" for non-markdown content (canvas, PDF, and text files — .txt, .csv, .json, .xml, .svg, .log, .yaml, .yml, .base); file results also carry extension (e.g. ".canvas", ".pdf", ".txt"). created is omitted when null. bytes is the on-disk file size. With include_leading_callout, each result also carries leading_callout ({ type, title, body }) when present.

vault_search_by_tagA

Find notes by frontmatter tag, ignoring letter case; inline #tags in note bodies are not searched. 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:

  • limit applies after sorting, so you get the most recently modified notes. Nothing in the response signals truncation: a result count equal to limit may mean more exist, so raise limit to check.

Behavior: Reads the search index, which picks up a file change within a few seconds, so a note written moments ago may not appear yet.

Errors:

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

  • "project/" matches neither "project" nor "project/a"; drop the "/".

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

Returns: JSON array of notes sorted by most recently modified, then by path ascending. Each note has path, folder (top-level folder; "" at the vault root), bytes (on-disk size), modified (file modification time), and the frontmatter values title (file name without .md when missing or not text), tags (every frontmatter tag Obsidian accepts, in the note's letter case), related ([] when missing), type (null when missing or not text), and created (null when missing or not an ISO date). Timestamps are ISO 8601. When present, a note also has leading_callout (the callout opening its body, as { type, title, body }) and additional_properties (every other frontmatter key).

vault_list_tagsA

List all tags in the vault with note counts, ordered by count descending, then by tag name. Only frontmatter tags Obsidian accepts are counted, not inline #tags. 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 — it supports hierarchical prefix matching ("project" matches "project/*").

Errors:

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

Returns: JSON array of { tag, count }; 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({ sort_by: "modified", 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. Prefer vault_search_by_folder for browsing a specific folder.

Parameters:

  • sort_by + limit interact: "modified" (default) uses filesystem mtime, which every note has. "created" uses the frontmatter created property; notes without a valid one sort after every dated note, so a small limit can leave them out — use "modified" to see them.

  • "modified" includes any file write (content edits, property changes, sync touches), so recently-synced notes appear recent even without user edits.

Errors:

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

Returns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted descending by chosen timestamp. created is null when the property is missing or not an ISO date; bytes is on-disk file size.

vault_search_by_folderA

Browse notes in a folder with full metadata (tags, type, related, created, modified) — unlike vault_list_notes, which returns paths only.

Example: vault_search_by_folder({ folder: "Projects" }) or vault_search_by_folder({ folder: "About Me", recursive: false })

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, not a text prefix: "Projects" matches notes under "Projects/" but not "ProjectsOld/". Matching ignores ASCII letter case, and a trailing slash is ignored.

  • limit applies after sorting, so you get the most recently modified notes. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.

Behavior: Reads the search index, which picks up a file change within a few seconds, so a note written moments ago may not appear yet. Notes in hidden (dot-prefixed) folders are never indexed, so never appear.

Errors:

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

Returns: JSON array of note metadata sorted by most recently modified, then by path: path, title, tags, related, folder, type, created (frontmatter; null when missing), modified (file time), bytes (on-disk size), plus, when present, leading_callout (the note's opening callout, { type, title, body }) and additional_properties (other frontmatter keys).

vault_list_property_keysA

Discover all property keys in the vault with note counts and sample values. Lets you understand the vault's metadata schema without reading individual notes.

Example: vault_list_property_keys() returns [{ key: "tags", count: 342, sample_values: ["session-log", "project"] }, ...]

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 and includes its subfolders: "Projects" covers "Projects/Archive" but not "ProjectsOld/". Matching ignores ASCII letter case; omit folder to scan the entire vault.

Behavior:

  • Only frontmatter 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 displayed strings. Values with the same string are grouped before choosing samples, counting each array element separately; ties use binary text order.

  • Checkbox values appear as "1" and "0"; null values are skipped.

Errors:

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

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 }, ...]

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.

Parameters:

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

  • folder names a whole folder and includes its subfolders: "Projects" covers "Projects/Archive" but not "ProjectsOld/". Matching ignores ASCII letter case.

  • limit applies after sorting by count descending, so you always get the most-used values first. Nothing in the response signals truncation: exactly limit values may mean more exist, which is common for keys with many distinct values like "title" or "created"; raise limit to check.

Behavior:

  • Handles both scalar properties (status: "active") and array properties (tags: ["a", "b"]). Array elements are unpacked and counted individually, so the sum of counts may exceed the note count.

  • Values with the same displayed string share one row with combined occurrence counts: number 1 and text "1" count together, while text "1.0" stays separate. Grouping happens before limit.

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

  • vault_search_by_property matches stored numbers numerically and text exactly; value "1" matches the number 1, the text "1", and a checked checkbox.

  • null values are skipped.

Errors:

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

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 frontmatter property matches a value — metadata-only search, no text query needed. Handles both scalar properties (status: "active") and array properties (tags, related).

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 (supports hierarchical prefix matching). Use vault_list_property_keys to discover valid keys and vault_list_property_values to see what values a key takes.

Parameters:

  • key matches exactly and case-sensitively; value matches stored text the same way, with no partial matching or globbing.

  • 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, "1e999") matches only as text. Numbers compare as 64-bit floats, so integers past 2^53 can compare equal and "1e-999" matches 0.

  • Pass a checkbox as "1" (checked) or "0" (unchecked); a checkbox matches only as text, so "1.0" and "true" do not match.

  • An array element must equal value in full: "blog" matches tags: ["blog", "draft"] but not tags: ["my-blog"].

  • folder names a whole folder and includes its subfolders: "Projects" covers "Projects/Archive" but not "ProjectsOld/". Matching ignores ASCII letter case; omit folder to search the entire vault.

  • limit keeps the most recently modified matches. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.

Errors:

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

Returns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by filesystem mtime descending — recently-synced notes may sort ahead of older content edits.

  • leading_callout appears only when the note has a leading callout.

  • additional_properties appears only when frontmatter has keys outside title, type, created, related, and the first key spelled tags in any letter case.

vault_get_backlinksA

Find all notes and canvases that link to a given note or canvas — captures [[wikilinks]], markdown, ![[embeds]], wikilinks inside frontmatter 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: exact vault-relative path including .md or .canvas extension, case-sensitive.

Returns: JSON with path (the queried note or canvas), backlinks (array of { path, title, bytes } sorted by title), and count. Backlink sources may be notes (.md) or canvas files (.canvas).

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 — if you expected links, find valid paths with vault_search, vault_list_notes, or vault_list_files.

vault_get_outgoing_linksA

Find all notes and files a given note or canvas links to. For notes: outgoing [[wikilinks]] and markdown. For canvas files: file-node references (vault-relative paths embedded in the canvas). Links inside code blocks are ignored; self-links are included.

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.

Parameters:

  • path: exact vault-relative path including .md or .canvas extension, case-sensitive. Matched against the search index, so the note or canvas must be indexed (file watcher processes new/moved files within seconds).

Returns: JSON with path, outgoing_links (array of { path, title, exists, kind, bytes, daily_note_forward_ref } sorted by target path), and count. Each link carries exists (boolean) and kind ("note"|"file"). When exists is true: kind "note" is a markdown note readable via vault_read_note; kind "file" is a non-markdown file (.canvas, image, PDF) readable via vault_read_file. When exists is false the link is broken (kind is always "note"). daily_note_forward_ref is true on broken links into the vault's daily notes folder, unless the daily notes folder setting is the vault root or blank — expected "create on click" navigation to a daily note that doesn't exist yet, not genuine breakage. bytes is the on-disk size of a note or file (null for broken links) — not the delivery cost: vault_read_file downscales images to fit response limits, so a large image is still cheap to read.

Errors:

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

  • A path not in the index returns an empty result (count 0), not an error — indistinguishable from a note with no outbound links.

vault_find_orphansA

Find notes with no incoming links from other notes or canvases — orphans are disconnected from the knowledge graph and may be forgotten or need linking. A note that only links to itself still counts as an orphan (self-links are ignored).

Example: vault_find_orphans({})

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:

  • With exclude_folders omitted, the defaults apply; the daily notes folder among them is re-read on each call: DAILY_NOTES_FOLDER, else .obsidian/daily-notes.json, else "Daily Notes" (also used when that file is unreadable). When that folder is the vault root or blank, no daily folder is excluded.

  • exclude_folders replaces the defaults, it does not add to them — list a default yourself to keep it (vault_get_daily_note's path starts with the daily notes folder whenever the defaults exclude one). Pass [] for no exclusions. Each entry names a whole folder, subfolders included ("Projects" also excludes "Projects/Archive" but not "ProjectsOld/"), ignoring ASCII letter case.

  • limit applies after exclusions and sorting by most recently modified. Nothing in the response signals truncation: exactly limit results may mean more exist, so raise limit to check.

Errors:

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

  • "too many excluded folders" — pass a shorter exclude_folders list, then retry.

Returns: JSON array of note metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties?), sorted by most recently modified. bytes is the on-disk file size.

vault_get_memoryA

Read semantic memory from About Me/ files. These are structured memory files containing dated bullet entries organized under H2 headings. With file: single file content. With file+section: just that H2 section's entries. No args: all files concatenated (frontmatter stripped) — can be large. Returns empty string when no memory files exist yet.

With file+on_or_after: returns structured JSON entries dated on or after the boundary date (inclusive), in document order (newest first when new entries go at the top, the default). Add section to scope to one H2 section; omit it to read every H2 section's entries in the file, each keeping its section attribution. It gives complete coverage from a known date without re-parsing the section.

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. Use on_or_after when you need entries from a known date forward (e.g. reconciliation boundaries). Prefer vault_read_note for reading non-memory notes.

Errors:

  • "section requires a file" / "on_or_after requires a file" — section or on_or_after was passed without file; add file

  • "memory file not found" — file does not exist in About Me/; call vault_list_memory_files to discover valid names

  • "memory file must not start with a dot" / "memory file must be a bare name without path separators" — pass the file's bare name: no folder or slash, and no leading dot (that would be a hidden file; memory files are always visible notes)

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

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

  • "memory file …: properties block is not valid YAML …" — the error names the file and says how to repair it; without file, one such file fails the whole read

Returns: Without on_or_after, raw markdown text. With on_or_after, JSON { entries, total, on_or_after } where each entry is { file, section, date, text } — text is the full raw entry markdown (bullet + continuation lines, wikilinks intact), same shape as vault_memory_recall entries. An empty match returns { entries: [], total: 0 }.

vault_list_memory_filesA

Discovery tool — lists About Me/ memory files with each file's heading outline, per-section entry counts, and leading callout. An entry is a dated bullet line ("- YYYY-MM-DD: text"). Does NOT return actual entries.

Example: vault_list_memory_files() returns [{ file: "Principles", title: "Principles", bytes: 2048, entry_policy: "append-only", leading_callout: null, headings: [{ level: 2, text: "Decision heuristics (newest first)", entry_count: 12 }] }, ...]

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. Always call this first to get valid file and section names, and to check a file's entry_policy before pruning entries.

Behavior: Lists every .md file directly inside About Me/, sorted by file name, whether or not it has frontmatter or a scope callout. 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.

  • "memory file …: properties block is not valid YAML …" — the error names the file and says how to repair it; one such file fails the whole listing

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); bytes is the on-disk file size; headings lists H1 and H2 headings in order, 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 via entry-policy frontmatter). 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 every relevant dated entry sorted oldest-first, so the full evolution of a preference, opinion, or fact is visible — semantic matching finds early entries even when their phrasing differs from the query. Tuned for recall over precision: expect some marginal entries and judge relevance yourself when synthesizing an answer. Content-word queries ("testing philosophy", "sustainable pacing") rank best. A meta-framed query ("opinions on testing") that scores below the relevance threshold degrades to relaxed 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.

Errors:

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

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

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. entries ascend by date (oldest first). total counts all matched entries; truncated=true means limit dropped the least-relevant matches — never a date range — so raise limit or narrow the query for the complete set. file is applied before limit, so limit keeps the most relevant matches from that file. search_mode is "hybrid" when vector matching contributed, "fts" when the entries came from keyword matching alone — including the any-term fallback that rescues a would-be-empty result; reranked is true when the cross-encoder relevance cut was applied.

vault_update_memoryA

Append a dated entry to a section of a memory file in About Me/. The server prefixes the date automatically ("- YYYY-MM-DD: entry text") and inserts newest-first by default. Idempotent — 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 frontmatter (surfaced by vault_list_memory_files) — a current-state file where pruning expired entries is expected maintenance rather than a violation.

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 (new sections get "(newest first)" appended; new files get a placeholder scope callout to fill in via vault_replace_in_note). A new section name nearly identical to an existing heading (an HTML-entity slip, typo, or spacing variation) is rejected, so a mistyped name cannot silently fragment the file — names differing only in digits (e.g. "2025" vs "2026") are distinct.

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:

  • "refusing memory write: … would shrink content" — safety guard: the write would leave the file at under half its size, which happens when the file holds content a rewrite cannot keep (for example long YAML comments in its properties). A retry returns the same refusal until a manual edit removes that content; inspect the file with vault_read_note to find it.

  • "entry must be a single line" — memory entries are single dated bullets; collapse newlines or append multiple entries.

  • "section must be a single line" — section names become H2 headings; remove line breaks.

  • "date must be a real ISO calendar date" — options.date only accepts an existing calendar date in bare YYYY-MM-DD form (e.g. "2026-07-02"), not a timestamp.

  • "entry contains a control character" / "section contains a control character" — the value includes a non-printable control byte; remove it before writing.

  • "memory file must not start with a dot" / "memory file must be a bare name without path separators" — use a bare file name: no folder or slash, and no leading dot (that would create a hidden file, invisible in Obsidian and to every listing).

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

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

  • "memory file …: the note would open with a properties block …" — the write would leave --- lines at the top of a file with no properties, around text that can't be kept as properties; 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/. Both date and entry text are required for exact matching — ensures only the intended entry is removed.

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 — a mistake, a misattribution, or something never true. Memory files are append-only by default, so do NOT delete to reflect a change: append the new state via vault_update_memory instead (newest-first naturally supersedes). The exception is a file whose frontmatter declares 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 entire non-protected notes.

Parameters:

  • date + entry together uniquely identify the bullet line within the given section. If multiple entries share the same date and text, deletion fails as ambiguous.

  • section scopes the match — an identical entry under a different heading is not found. Section matching is case-insensitive, with or without the "(newest first)" suffix.

Errors:

  • "memory file not found" — file does not exist in About Me/; call vault_list_memory_files to discover valid names.

  • "memory file must not start with a dot" / "memory file must be a bare name without path separators" — pass the file's bare name: no folder or slash, and no leading dot (that would target a hidden file; memory files are always visible notes).

  • "date must be a real ISO calendar date" — date only accepts an existing calendar date in bare YYYY-MM-DD form. A hand-edited bullet carrying an impossible date cannot be targeted by this tool — remove it with vault_delete_span or a manual edit.

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

  • "no entry matching …" — no bullet matched the given date and entry text; verify exact text via vault_get_memory(file, section).

  • "ambiguous: N entries match …" — more than one identical bullet exists in the section (e.g. from hand edits, sync conflicts, or entries predating duplicate protection; vault_update_memory refuses to write exact duplicates). Remove the extra copy with vault_delete_span (pass first_match: true — identical lines make every anchor ambiguous) or a manual edit, then retry.

  • "refusing memory write: … would shrink content" — safety guard blocked a write that would remove more than half the file. Re-read with vault_get_memory to confirm current content; an entry that really is that large needs vault_delete_span or a manual edit.

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

  • "memory file …: the note would open with a properties block …" — the write would leave --- lines at the top of a file with no properties, around text that can't be kept as properties; 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. Each setting comes from the DAILY_NOTES_FOLDER / DAILY_NOTES_FORMAT env vars, falling back to the vault's .obsidian/daily-notes.json (a blank folder there means Obsidian's new-note folder from .obsidian/app.json, the vault root by default; either file counts as absent when unreadable), then to "Daily Notes" and YYYY-MM-DD.

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. Use vault_recent_notes to review recent vault activity around a date (not date-filtered — returns globally recent notes).

Parameters:

  • date: past and future dates are both valid — the tool resolves the configured path for any date and reports exists: false if the note hasn't been created yet.

Errors:

  • "invalid date" — use YYYY-MM-DD format

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

  • "daily note format contains unsupported token(s): ..." — the configured format uses tokens the server cannot reproduce (ordinals like Do/Mo, dd, d, e, k/kk, w, Q, Z/ZZ, or the L-family localized formats); change the format in Obsidian or set DAILY_NOTES_FORMAT to a supported alternative

  • "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; change it in Obsidian, or set DAILY_NOTES_FOLDER and DAILY_NOTES_FORMAT

  • "EACCES: …" or another filesystem error code — the daily note's file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it

Returns: JSON with path (string — resolved vault-relative path), content (string|null — full note body, 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, ⏳ scheduled, 🛫 start, ➕ created, ✅ done, ❌ cancelled, 🔺⏫🔼🔽⏬ priority, 🔁 recurrence, 🏁 onCompletion, 🆔/⛔ dependencies) and Dataview inline fields ([due:: 2026-07-04], [priority:: high], ...). Every result carries its attribution — note path, folder, line number, and the nearest heading when the task sits under one (the lane on a Kanban board) — so no follow-up reads are needed to locate a task. Task lines inside fenced code blocks and %% %% comment blocks are not indexed. Checkboxes typed NON_TASK in the Tasks plugin's status registry 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 Example: vault_list_tasks({ top_level_only: true, path: "TASKS.md" }) — board cards only, excluding checklist sub-items

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: Reads the search index, which picks up a file change within a few seconds, so a task written moments ago may still show its old state.

Parameters:

  • status: virtual values 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.

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" — the 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 .md" — add the ".md" extension

  • tag must not be empty after its leading "#" — tag is "#" alone; pass the tag name

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

Returns: JSON { total, tasks }. Every task carries path, line, status, status_char, description, folder, depth (0 for top-level, 1+ for sub-tasks), is_kanban_task, 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), and subtask_progress — { done, total } over the task's DIRECT checklist children, present only when the task has a checklist; done counts status "done" only (a cancelled child counts toward total, not done), and the counts ignore the query's filters — so a filtered or top_level_only read still shows each card's checklist progress.

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 (the Tasks plugin's todo symbol, [ ] by default), with its created date (➕) set to today; set in_progress later with vault_update_task. Metadata uses the format the vault's Tasks plugin is configured for (emoji unless its config says Dataview); override it only at the user's request, since the plugin reads just one format.

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; no block_id on a Kanban sub-task 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. Guarantees correct field ordering (description → priority → 🔁 recurrence → 🏁 on_completion → ➕ created → 🛫 start → ⏳ scheduled → 📅 due → 🆔 task_id → ⛔ depends_on → ^block_id) so the card round-trips through vault_list_tasks with all fields intact. 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: the same pair vault_update_task uses (block_id / 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. When the Kanban plugin saves the board, it copies an indented line's block ID onto its card, replacing the card's own ID.

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

  • priority: the plugin ranks "no signifier" (normal priority) between medium and low.

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

  • due / scheduled / start: omit a date rather than guessing — an absent 📅 means "no deadline".

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" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it

  • "heading required for Kanban boards" — kanban-plugin note without heading; pass heading with the target lane

  • "heading "X" not found; available: ..." — no heading matches; the error 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 (message names the blockId or line tried), or the line is inside 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" — the parent's checkbox character (c) is typed NON_TASK in the Tasks plugin's status settings, so it is not a task; the user must change its type there, then restart the server

  • "no checkbox symbol for status ..." — the Tasks plugin's status settings have no symbol for todo, and the default [ ] character has another type; the user must add a todo symbol there, then restart the server

  • "parentBlockId and parentLine are mutually exclusive" — both parent_block_id and parent_line were passed; drop one

  • "parent and heading are mutually exclusive" — a parent (parent_block_id or parent_line) and heading were both passed; drop one

  • "blockId ... already exists in this note" — pick a block_id not yet used in the note

  • "blockId ... contains invalid characters" — block_id must match [a-zA-Z0-9-]+

  • "blockId is required" — pass block_id

  • "blockId is not allowed on a sub-task on a Kanban board" / "subtask ... ends in a block ID" — drop the block_id, or the trailing ^id from the checklist item

  • "description is empty" / "subtasks cannot contain an empty item" — whitespace-only description or checklist item; pass visible text

  • "description must be a single line" / "subtasks items must be a single line" — a task is one file line; a line break in the text would split its metadata onto a line the parser never reads; remove the line breaks

  • "taskId ... contains invalid characters" / "dependsOn entry ... contains invalid characters" — task_id and every depends_on entry must match [a-zA-Z0-9_-]+ (the Tasks plugin's id grammar)

  • "unrecognized recurrence rule ..." — the rule text is not Tasks-plugin natural language; written as-is it would silently never recur; use a rule such as "every week" or "every 2 weeks when done"

  • "invalid date" — a date param fails calendar validation; pass a real YYYY-MM-DD date

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

  • "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 …" — the edit would leave --- lines at the top of a note with no properties, around text that can't be kept as properties; the error says how to avoid it

Obsidian syntax: The Tasks plugin reads metadata off the END of a task line. When description or subtasks text ends in a signifier the plugin's parser recognizes as a field (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, not text. Whether it is captured depends on the field's value grammar: 🔁 reads any trailing words as its recurrence rule, while 📅 followed by non-date words stays description text. Captured text can also change the value read back for the field beside it, or add a field the call never set. The write still succeeds either way; when the stored line would read back differently than submitted, the result carries an advisories array naming each divergence; reword the text to avoid one.

Returns: JSON { path, line, description, block_id, heading, subtasks, changes, advisories } — line is the new task's 1-based line number in the file; description is the text as submitted; 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) — checklist items are written without a block_id, so line is the handle for a follow-up update; 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, dependencies, block_id, checklist items, or heading placement in one call. Any combination of these can change together — every field passed is written in a single edit.

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; a recurring task (🔁) spawns its next occurrence Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", recurrence: "every week" }) — make a task recurring (null removes the rule) Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", heading: "Done" }) — move a task to a different heading (lands at the top of the lane by default; 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 Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", heading: "Active", position: 3 }) — move to the 3rd position in a lane

When to use: Any change to an existing task — completing, starting, re-prioritizing, editing text, setting or clearing dates, adding checklist items, assigning block_ids, moving between headings, or reordering within a lane. 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 sub-task on a Kanban board has no block_id, so re-read its line with vault_list_tasks just before an update by line: each save by the Kanban plugin rewrites the board, and the listing can take a few seconds to catch up with a save.

  • 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 (nested checkboxes left as they are); a sub-task stays under its parent. Recurring (🔁), on "done" only: spawns the next occurrence above the completed one, dates advanced per the rule; when the Tasks plugin puts the next recurrence on the line below, the spawn goes there and the completed task's nested lines stay with it. 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 spawn is created first, then the completed task is removed. Result carries on_completion_applied: "delete".

  • 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 spawn. 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. Omitting position performs no reorder. Ignored when the task is deleted on completion. Not valid on sub-tasks.

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" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it

  • "exactly one of blockId or line is required" / "blockId and line are mutually exclusive" — pass exactly one of block_id or line

  • "blockId ... not found" — no task line in the note ends with ^block_id; check the id with vault_list_tasks, or target by line

  • "blockId ... is inside a fenced code block or comment" — the only lines ending in that ^id sit inside a fenced code block or %% %% comment, which hold no tasks; check the id, or target by line

  • "blockId ... matches N task lines" — more than one task line ends in the id (the message lists their line numbers); target the intended task by line. To repair on a regular note, give all but one line a new id with assign_block_id. On a Kanban board, where the plugin copied a sub-task's id onto its card, remove the id from the sub-task's line (assign_block_id: null, by line). The card still holds the copied id, so then assign the card's line its original id (links to the card still name it) or a new one

  • "no task at line N" — line doesn't contain a task checkbox; re-read line numbers with vault_list_tasks, or target by block_id

  • "line N is inside a fenced code block or comment" — the line is inside a fenced code block or %% %% comment; target a line outside the fence

  • "checkbox "[c]" is a NON_TASK status" — the task's checkbox character (c) is typed NON_TASK in the Tasks plugin's status settings, so it is not a task; the user must change its type there, then restart the server

  • "no checkbox symbol for status ..." — the Tasks plugin's status settings have no symbol for the status the message names, and its default character has another type; the user must add one there, then restart the server

  • "at least one mutation" — no change params provided; pass at least one field to change

  • "cannot move a sub-task to a heading" — explicit heading on a task nested under another task (depth > 0 in vault_list_tasks); move its parent instead

  • "cannot reposition a sub-task" — explicit position on a sub-task (sub-tasks move with their parent); 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; pass heading to move it into a section

  • "cannot reorder within "X" — the heading appears N times" — same-lane reorder on a card whose heading name is duplicated in the note; rename one section to make it unique

  • "cannot place at position P under "X" — the heading appears N times" — cross-lane move with an integer position to a heading name that appears more than once; use "top" or "bottom" (they place under the first match), or rename one section to make it unique

  • "heading "X" not found; available: ..." — target heading doesn't exist; the error lists the note's headings

  • "multiple done lanes detected" — status "done" on a Kanban board with more than one lane marked complete (a Complete line under its heading); pass heading to pick the lane

  • "no done lane detected" — status "done" on a Kanban board with no Complete line and no "Done" heading; pass heading explicitly

  • "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" — a date param fails calendar validation; pass a real YYYY-MM-DD date

  • "description cannot be empty" / "addSubtasks cannot contain an empty item" — whitespace-only description or checklist item; pass visible text

  • "description must be a single line" / "addSubtasks items must be a single line" — a task is one file line; a line break in the text would split its metadata onto a line the parser never reads; remove the line breaks

  • "taskId ... contains invalid characters" / "dependsOn entry ... contains invalid characters" — task_id and every depends_on entry must match [a-zA-Z0-9_-]+ (the Tasks plugin's id grammar)

  • "unrecognized recurrence rule ..." — the rule text is not Tasks-plugin natural language; written as-is it would silently never recur; use a rule such as "every week" or "every 2 weeks when done"

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

  • "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 …" — the edit would leave --- lines at the top of a note with no properties, around text that can't be kept as properties; the error says how to avoid it

Obsidian syntax: The Tasks plugin reads metadata off the END of a task line. When description or add_subtasks text ends in a signifier the plugin's parser recognizes as a field (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, not text. Whether it is captured depends on the field's value grammar: 🔁 reads any trailing words as its recurrence rule, while 📅 followed by non-date words stays description text. Captured text can also change the value read back for the field beside it, or add a field the call never set. The write still succeeds either way; when the stored line would read back differently than this call set, the result carries an advisories array naming each divergence; reword the text to avoid one. The ✅/❌ dates a status change adds or removes produce no advisories by themselves. A description signifier that changes how a stamped date parses back is still reported.

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. 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. It carries no block_id, so line is its handle.

  • changes lists every field applied as "field: before → after", with "(none)" for an absent value. add_subtasks appears as "subtasks", its two sides checklist-item counts, and a spawn adds "next_occurrence: (none) → line N".

  • 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, preceded by a metadata line stating the window and total line count Example: vault_read_file({ path: "papers/research.pdf" }) — structured text with title, headings, and links

What each file type returns:

  • Images (.png/.jpg/.jpeg/.gif/.webp): the image as a viewable image block — downscaled and recompressed server-side when it exceeds the image output budget (MAX_IMAGE_OUTPUT_BYTES, 49152 bytes) or 1568 pixels on its longer side, delivered untouched otherwise — plus a text line stating the path, delivered format/dimensions/bytes, and the original dimensions when shrunk. Animated GIFs are reduced to their first frame when recompressed.

  • Canvas (.canvas): a readable markdown outline per JSON Canvas 1.0 — groups (by visual containment), node content in reading order, and a connections list with edge labels.

  • PDFs (.pdf): structured text with document metadata — title, page count, heading hierarchy (from font sizes relative to the body text), code blocks and inline code (from monospace fonts), page separators, and a deduplicated links footer. Richer than flat text extraction: headings, code, and hyperlinks that flat extraction loses are preserved.

  • Text formats (.svg/.json/.txt/.csv/.xml/.log/.yaml/.yml/.base): the file content verbatim as text. .svg is returned as its XML source; .base as its YAML source.

raw: true switches a canvas or PDF to its other form:

  • Canvas: the exact JSON source (geometry, ids, colors — full fidelity) instead of the outline.

  • PDF: each page rendered and returned as an image block instead of extracted text, showing layout, diagrams, tables, and formatting that text extraction cannot preserve. Image-only and scanned PDFs work in raw mode. Only the first 5 pages are rendered; the text read (without raw) covers every page.

Line paging: start_line and limit page any text result — text formats, canvas outlines and raw JSON, PDF-extracted text — as a 1-based line window, 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)"). The text output cap (100 KiB) applies to each window, so one very long line can still overflow it; paging never gets around the file-size cap. Paged windows come back with \n line endings and no trailing newline; a read without paging inputs stays byte-exact.

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 — this tool rejects them. To check a large file's line count before reading it whole, request start_line: 1 with limit: 1 — one line plus the total.

Errors:

  • "not a file" — the path ends in .md; read notes with vault_read_note

  • "file not found" / "ENAMETOOLONG: …" — nothing exists at that path, or the path (or a name in it) is longer than the filesystem allows; discover valid paths via vault_list_files

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it (hidden files are not readable, matching Obsidian)

  • "file too large" — the file exceeds the file-size cap (MAX_FILE_BYTES, default 50 MiB)

  • "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; paging applies only to text results, and an image always comes back as its image block

  • "not valid UTF-8" — the file's bytes aren't UTF-8 text; returning them would silently corrupt the content

  • "invalid .canvas JSON" — the canvas file is empty or not valid JSON, so no outline can be built; 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" — the PDF needs a password, which this tool cannot supply; 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; replace or re-export the file

  • "PDF page rendering failed" — raw: true was set and the PDF opened, but no page could be rendered; 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" — the image could not be compressed under the image output budget

  • "EACCES: …" or another filesystem error code — the file can't be read (permissions, a symbolic link loop); ask the vault's owner to fix it

  • unsupported types (audio, archives, …) return an error naming the readable types plus the file's existence and size

Returns: for images, an image content block plus a one-line metadata text block; for PDFs with raw: true, a metadata text block followed by alternating image and text blocks (one pair per page); for every other supported type, a single text content block — preceded by a window-metadata text block when start_line or limit was given.

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({}) — every non-markdown file in the 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: Reads the filesystem rather than the search index, so use the folder's exact letter case; on a case-sensitive filesystem a different case finds nothing.

Errors:

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

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

Returns: JSON with files (array of { path, extension, bytes }, sorted by path; extension is "(none)" for a file without one), extension_counts (per-extension totals over the full filtered set), total (full filtered count), and truncated (true when total exceeds limit). bytes is the on-disk file size, not the delivery cost: reading an image via vault_read_file returns a copy shrunk to fit when needed, so a large listed image is still cheap to read. Text formats return verbatim, so their listed size is what a read delivers; one over 100 KiB must be read in windows with vault_read_file's start_line and limit. Only supported types are readable via vault_read_file.

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.4/5.0

Scored across 33 tools

Disambiguation3/5

Multiple note-editing tools (vault_replace_in_note, vault_replace_span, vault_insert_at_anchor, vault_patch_note, vault_delete_span) overlap in purpose, and several search/list tools cluster around discovery. The descriptions give explicit 'prefer X' guidance, but the set still presents a thicket of similarly purposed primitives that an agent must carefully navigate.

Naming Consistency4/5

All tools use a common vault_ prefix and snake_case, and most follow a verb_noun pattern (list_notes, create_task, get_backlinks). A few names break the verb-first convention (vault_memory_recall, vault_recent_notes), and vault_search is bare, but the overall consistency is high.

Tool Count2/5

33 tools is heavy for an Obsidian MCP server. Many capabilities (multiple replacement/insertion/patch primitives and several search variants) could be consolidated into fewer parameterized tools, and the count exceeds typical well-scoped server sizes.

Completeness4/5

Core note lifecycle, search, memory, tasks, links, daily notes, and properties are broadly covered with deep operations. Gaps remain in writing or deleting non-markdown files and editing canvas files, but most agent vault workflows are supported.

Maintenance

ActivityActive
ResponsivenessResponsive