Skip to main content
Glama
aliasunder

Vault Cortex Obsidian MCP Server

CI Gitleaks Trivy GitHub Release npm OpenSSF Scorecard OpenSSF Best Practices Ask DeepWiki vault-cortex MCP server

Vault Cortex is a standalone MCP server that gives any AI agent hybrid search, task management, structured memory, and read/write access to your Obsidian vault. No plugins, no running Obsidian, no separate bridge. One Docker container, your vault folder, a full tool suite + guided prompts. Run it on a remote server with Obsidian Sync, and the same vault is accessible from your phone, claude.ai, or any remote MCP client, secured with OAuth 2.1. Deploy it with one click or self-host it; either way, the vault is always yours.

Contents — What you get · Quick Start · How It Works · Hybrid Search · Memory · Tasks · Files · Tools · Prompts · Properties · Config · Daily Notes · Data Integrity · Auth · Deployment · One-click Deploy · Community Deployments

What you get

  • Remote access — works from your phone, a remote server, or any MCP client via OAuth 2.1. One click on Render or Railway gets you there with no server to manage; a VPS works too.

  • Plugin-free — Obsidian doesn't need to be running. The server works directly with .md files on disk. Headless sync keeps the vault current.

  • Hybrid search — FTS5 keyword matching + vector semantic similarity via RRF fusion, refined by cross-encoder reranking for intent-heavy queries. Keywords stay precise on exact terms and jargon; vectors find notes even when your words differ from the vault's.

  • Structured memory — dated, append-only entries accumulate into a personal knowledge layer, auto-initialized for AI personalization. Topic recall answers "what do I think about X?" with the current take and the dated history behind it — evolution included.

  • Tasks — Kanban-aware task queries and updates: triage by status, dates, or priority, then complete, reprioritize, or move tasks between lanes in one call. Completing a recurring task spawns its next occurrence. Parses both Tasks plugin emoji and Dataview inline-field formats.

  • Link graph — backlinks, outgoing links, and orphan detection across the vault

  • Files — read the vault's non-markdown files too: images arrive as actual images (shrunk to fit when needed), PDFs as structured text or rendered pages, canvases as readable outlines, data files as text

  • Obsidian-native — understands frontmatter, wikilinks, tags, headings, and daily notes

  • Guided workflows — built-in prompts for vault health, memory review, and daily reconciliation — assembled from live vault data each time

Tested across a 15-day trip through Europe. 30+ sessions from a phone, 216 tool calls, zero laptop access needed. Writes in one session were immediately available in the next, across cities and days.


Related MCP server: Vault MCP Server (mschuchard)

Quick Start

Local (2 minutes — Docker + your vault folder)

Prerequisites: Docker (or a Docker-compatible runtime, e.g. OrbStack, Colima, Podman), Node.js >= 22.12 (only for the CLI — the server itself runs in Docker), and an Obsidian vault (or any folder of .md files).

npx vault-cortex@latest init

That's it — the CLI asks for your vault path, generates the auth token and config files, starts the server, and prints the connection details for your MCP client (CLI reference →).

Set up with the CLI? It manages the server from here on — configure, upgrade, start, restart, logs, down (CLI reference →).

Set up with Compose? Stick with Compose for updates too (docker compose pull && docker compose up -d) — the CLI and Compose manage the container independently.

# 1. Get the quickstart files
curl -O https://raw.githubusercontent.com/aliasunder/vault-cortex/main/deploy/local/docker-compose.yml
curl -O https://raw.githubusercontent.com/aliasunder/vault-cortex/main/deploy/local/.env.example

# 2. Configure
cp .env.example .env
# Edit .env — set MCP_AUTH_TOKEN (openssl rand -hex 32) and VAULT_PATH

# 3. Start
docker compose up

Full local guide → (includes Windows setup)

Remote (access from anywhere)

Your vault on a server, kept current by Obsidian Sync, reachable from your phone, claude.ai, or any MCP client. The one-click options ask for your vault name and timezone (plus the vault password if your vault is encrypted), then handle HTTPS, restarts, a generated MCP token, and persistent storage. Once deployed, a setup page walks you through signing in to Obsidian Sync in your browser. On your own server the CLI asks for the public URL and vault name, captures the Sync token for you, and generates the MCP token; HTTPS is yours to set up.

Railway

Render

Self-hosted

Deploy on Railway

Deploy to Render

CLI setup →

Account

Railway on the Hobby plan or higher — the 5 GB volume is included

Render with a card on file

A VPS with Docker

Cost

Usage-metered: typically $20–30 USD/mo for a personal vault — a little under Render for a quiet vault, a little over for a busy one

Flat: about $26 USD/mo for the Standard instance (2 GB) and 5 GB disk, billed by the second

Whatever your VPS costs

Pick it if

You want the easier start — the template lands you in a configured project

A predictable bill matters more than setup polish

You already run a server or want full control

Guide

Railway guide →

Render guide →

Remote guide →

All three need an Obsidian Sync subscription. Whichever you pick, the server is replaceable and your vault isn't — it stays in plain Markdown in Obsidian Sync and on your devices; the container only holds a copy.

The setup page. Deploy without an Obsidian Sync token and the server starts in setup mode: opening its URL in a browser lands on a sign-in page at /setup. Enter your Obsidian account credentials once (two-factor supported) — you sign in with Obsidian directly; the server keeps only the Sync token from that sign-in, restarts, and downloads your vault.

Self-hosted: your own VPS

The Vault Cortex CLI sets up the same container on any Linux box you run — you manage the server, the image, and updates. You need Node.js >= 22.12 for the CLI itself; the server runs in Docker.

# On your VPS:
npx vault-cortex@latest init --mode remote

That's it — the CLI walks through the public URL, Obsidian Sync token (it can run get-sync-token for you), vault name, the vault password for an encrypted vault, and auth config, then starts the server (CLI reference →).

Set up with the CLI? It manages the server from here on — configure, upgrade, start, restart, logs, down (CLI reference →).

Set up with Compose? Stick with Compose for updates too (docker compose pull && docker compose up -d) — the CLI and Compose manage the container independently.

# On your VPS:
mkdir -p /opt/vault-cortex && cd /opt/vault-cortex
curl -O https://raw.githubusercontent.com/aliasunder/vault-cortex/main/deploy/remote/docker-compose.yml
curl -O https://raw.githubusercontent.com/aliasunder/vault-cortex/main/deploy/remote/.env.example
cp .env.example .env
# Edit .env — set MCP_AUTH_TOKEN, PUBLIC_URL, VAULT_NAME (OBSIDIAN_AUTH_TOKEN optional — /setup handles it)
docker compose up -d

Left OBSIDIAN_AUTH_TOKEN empty? Once the container is up, open <PUBLIC_URL>/setup in your browser and sign in — set up HTTPS first, since the page sends your Obsidian password to the server (full walkthrough →).

Connect your MCP client

Setup

Server URL

Local

http://localhost:8000/mcp

Remote (one-click)

https://<host>/mcp — <host> is the domain Render or Railway shows on the service page

Remote (self-hosted)

<PUBLIC_URL>/mcp

Add the server URL in any MCP client — Claude Code, Claude Desktop, Cursor, OpenCode, or any other. OAuth clients open a consent page in your browser — approve with your token, and the client handles token renewal from then on. Clients without OAuth (MCP Inspector, scripts) send the token directly as an Authorization: Bearer header.

Claude Code:

claude mcp add --scope user --transport http vault-cortex http://localhost:8000/mcp   # local (or <PUBLIC_URL>/mcp)

--scope user registers the server for every project; omit it to scope it to the current directory only.

A remote server with a publicly reachable https URL adds directly in Claude Desktop's "Add custom connector" dialog — no file editing. Any http URL — localhost included — is rejected by that dialog, so register it in claude_desktop_config.json instead (Claude Desktop → Settings → Developer → Edit Config opens the file) through the mcp-remote stdio bridge:

{
  "mcpServers": {
    "vault-cortex": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "http://localhost:8000/mcp",
        "--header",
        "Authorization: Bearer <your MCP_AUTH_TOKEN>"
      ]
    }
  }
}

claude.ai (web and mobile) connects to the remote setup only — its connectors are fetched server-side and can never reach localhost.

"Remote MCP server" refers to the connection type (HTTP) — in the local setup the server still runs entirely on your machine.

See Authentication for both methods and token lifetimes.


How It Works

Everything runs in one Docker container, working directly with the .md files on disk:

  • Your vault stays the source of truth — the server reads and writes the same plain Markdown files your Obsidian apps do.

  • Search is derived data — a file watcher keeps the index (keywords + vectors) current as notes change, and it can be rebuilt from your notes at any time.

  • The remote image adds a sync loop — a bundled Obsidian Sync service keeps the container's vault current with every device: edit a note on your phone and it's searchable moments later; an agent writes a note and it shows up in Obsidian.

graph LR
    subgraph container ["One Docker container"]
        Sync["sync service<br/>(remote image)"]
        Vault[("/vault<br/>.md files — source of truth")]
        Index[("search index<br/>keywords + vectors")]
        Server["MCP server"]
        Sync <-->|read/write| Vault
        Vault -->|file watcher| Index
        Server <-->|read/write| Vault
        Server -->|query| Index
    end
    Obsidian["Your Obsidian apps<br/>(phone, laptop)"] <-->|Obsidian Sync| Sync
    Client["Any MCP client<br/>(Claude, Cursor, claude.ai)"] -->|OAuth 2.1 / Bearer| Server

See ARCHITECTURE.md for the full design, auth flow diagrams, and component breakdown.


Keyword search alone fails when your vocabulary doesn't match the vault's — "aspirations" won't find a note about "targets", "coworkers" won't surface your "references" file. In testing against a real vault, 30% of natural-language queries returned zero or tangential results with keywords alone. Hybrid search eliminated those misses in the same test.

Hybrid search fuses the keyword and vector rankings via Reciprocal Rank Fusion, then the reranker refines the fused result:

  • Keywords (FTS5) stay precise on exact terms, jargon, and property values

  • Vectors (sqlite-vec) bridge the vocabulary gap by matching on meaning

  • Reranker (cross-encoder) refines ordering by scoring each query-document pair jointly — rescues intent-heavy queries where keywords and vectors both miss

All models run locally (~45MB total, no external API). Set EMBEDDING_ENABLED=false for keyword-only search, or RERANK_MODE=none to skip reranking for lower latency.

See ARCHITECTURE.md → Hybrid Search for model details, blend weights, and the full pipeline breakdown.


Memory

A memory layer that only grows is only useful if agents can retrieve the right entries without reading everything back every time. Once you have hundreds of dated entries across multiple files — preferences, principles, communication style, ongoing commitments — whole-file reads bury the signal in irrelevant material. The memory system is designed for targeted retrieval.

The layer is a folder of plain Markdown files (default: About Me/) holding dated entries under topic headings — auto-created with starter templates on first run, grown by agents through vault_update_memory. Three properties make it work:

  • Append-only — entries are never overwritten; corrections arrive as new dated entries. The layer becomes a personal knowledge base that captures your current state and the evolution behind it

  • Topic recall — vault_memory_recall retrieves every relevant entry across all memory files at once, matched by keyword and by meaning, oldest first. Ask "what do I think about X?" and get the current take plus the dated history of how it developed — no need to read entire files or guess which file holds what

  • Grows without degrading — capping results (limit) drops the least-relevant entries, never a slice of the timeline. A memory layer with 500 entries serves a targeted query as well as one with 50

Files that describe what's current rather than what has been true (routines, active commitments) can declare entry-policy: living in frontmatter — their expired entries are prunable rather than preserved, keeping the current-state picture accurate.

The whole layer is optional — set MEMORY_ENABLED=false to hide the memory tools and skip the folder auto-creation entirely.

See ARCHITECTURE.md → Memory for the recall pipeline, indexing model, auto-initialization, and opt-out behavior, and templates/memory for the file format, entry-policy convention, and starter templates.


Tasks

Task metadata lives in plain markdown — scattered across files, encoded in emoji signifiers or inline fields, organized under Kanban headings. An agent answering "what's overdue?" would need to parse every file and understand your chosen format; completing a task on a Kanban board means knowing the board's lane structure, the date syntax, and which heading is the done lane.

The task layer handles this so agents don't have to:

  • Find — filter by status, six date fields (due, scheduled, start, created, done, cancelled), priority, folder, or Kanban lane. Each result carries its note path, line number, and nearest heading when the task sits under one (the lane on a Kanban board) — no follow-up reads needed to locate a task

  • Create — add a correctly-formatted task in one step: description, priority, dates, recurrence, "On completion" action, dependencies, block ID (^id), and checklist sub-items (text only, no dates), placed under a heading at the top, the bottom, or an exact position, or nested under a parent task as a sub-task with its own dates. On a Kanban board a sub-task gets no block ID, since the Kanban plugin copies it onto the card when it saves the board

  • Update — complete, reprioritize, edit the text, set or clear dates, recurrence, and the "On completion" action, add checklist items, move tasks between headings, and reorder within a lane in a single call

  • Complete — marking a task done auto-detects the done lane and stamps the completion date, honoring the plugin's "Set done date" setting; reversing it removes the date. Completion also runs the Tasks plugin's own behaviors:

    • a recurring task spawns its next occurrence, dates advanced the way the plugin computes them

    • a task set to delete "On completion" disappears from the note

  • Both formats — whichever format you use, Tasks plugin emoji signifiers or Dataview inline fields, the server reads both and writes in the format your Tasks plugin is configured for — read from the plugin's settings when your vault syncs .obsidian/, with emoji signifiers as the default otherwise

See ARCHITECTURE.md → Tasks for the indexing model, date cascade sorting, and Kanban lane detection.


Files

Your notes embed screenshots, reference architecture diagrams, and link out to canvases and data files — but to an agent reading markdown, ![[diagram.png]] is just text. Vault Cortex treats files as part of the vault rather than clutter around it — linked, sized, and readable, each in the form an agent can use:

  • Images — the image itself, not the filename. Screenshots and diagrams are downscaled and recompressed server-side when they exceed what MCP clients accept, so even a phone session can look at a 5MB architecture diagram

  • Canvases — a Canvas board arrives as a readable outline: its groups, each card's content in reading order, and the connections between them. Canvas content is full-text searchable, and file references on the board appear in the link graph — backlinks and outgoing links work just like note-to-note links. The exact JSON source is one flag away when full fidelity matters

  • PDFs — text is extracted with heading hierarchy, code blocks, and hyperlinks preserved; PDF content is full-text searchable alongside your notes. Set raw: true to render pages as images instead, showing layout, diagrams, and tables that text extraction can't preserve — scanned and image-only PDFs work in this mode

  • Text and data files — TXT, SVG, JSON, XML, CSV, YAML, logs, and Bases files return exactly as written; the first 100 KB of content is full-text searchable. Big data files and logs can be read a line range at a time, with each page reporting where you are and how much file remains

  • Browse — list any visible folder's files with per-extension counts and file sizes; files a note links to report their size in the link graph too

Set FILE_TOOLS_ENABLED=false to hide the file tools — useful when your remote vault syncs without attachments.

See ARCHITECTURE.md → Files for the image pipeline and dispatch model.


Tools

Category

Tool

Description

Vault CRUD

vault_read_note

Read a note — full body, properties, outline, or a section

vault_write_note

Create a note (fails if it already exists; set overwrite to replace)

vault_patch_note

Heading-targeted edit (append, prepend, replace with include_children guard, insert)

vault_replace_in_note

Find-and-replace text in a note (first match or replace_all_occurrences)

vault_delete_span

Delete a block of lines by short anchors, no full re-quote

vault_replace_span

Replace a block of lines by short anchors with new content

vault_insert_at_anchor

Insert content before or after a line identified by a short anchor

vault_list_notes

List notes with optional glob/folder filter

vault_delete_note

Delete a note, honoring the vault's trash setting (protected paths enforced)

vault_move_note

Move or rename a note, rewriting links across the vault

Search

vault_search

Hybrid search with tag/folder/property/date filters

vault_search_by_tag

Find notes by tag (exact or prefix match)

vault_search_by_folder

Browse notes in a folder with metadata

vault_recent_notes

Recently modified or created notes

vault_list_tags

All tags with usage counts

Tasks

vault_list_tasks

Vault-wide task index with sub-task depth — Kanban-aware, date/priority/heading filters

vault_create_task

Create a correctly-formatted task — dates, priority, recurrence, on_completion, sub-tasks, block_id

vault_update_task

Edit any task field in one call — completing a recurring task creates its next occurrence

Memory

vault_get_memory

Read structured memory (file, section, or all)

vault_update_memory

Append a dated entry to a memory section

vault_delete_memory

Remove a specific memory entry by date

vault_list_memory_files

Discover memory files, their sections, and each file's entry policy

vault_memory_recall

Entry-granular hybrid recall of a topic across memory files, oldest-first

Properties

vault_list_property_keys

All property keys with sample values

vault_list_property_values

Distinct values for a property key

vault_search_by_property

Find notes by property key-value

vault_update_properties

Add, update, or replace properties without touching the body

Links

vault_get_backlinks

Notes linking to a given path

vault_get_outgoing_links

Links from a given note

vault_find_orphans

Notes with no incoming links

Files

vault_read_file

Read a non-markdown file — images delivered as images, canvases as readable outlines

vault_list_files

Browse the vault's non-markdown files with sizes and per-extension counts

Daily Notes

vault_get_daily_note

Today's (or any date's) daily note


Prompts

Tools are model-driven — the assistant calls them. Prompts are workflows you trigger. Each one queries the search index, link graph, and memory layer at invocation time, then assembles the results with guided instructions — so the session starts grounded in your vault's actual state, not assumptions.

Prompt

Arguments

What it does

vault-orientation

—

Surveys vault stats, folder distribution, property adoption rates (flags low adoption), orphans, broken link count, tags, recent notes, and the memory layer — with contextual tool suggestions

memory-review

file?, max_chars?

Structural overview (scope callouts, section entry counts) + dated content as a timeline. Guided reflection: evolution narrative, scope-fit, backfill gaps, and coverage analysis — append-only by default, pruning proposed only for entry-policy: living files. Hidden when MEMORY_ENABLED=false, READONLY_MODE=true, or DISABLED_TOOLS includes vault_update_memory.

daily-review

date?, max_chars?

Reconciles a day — daily note, vault-wide task status (due/overdue, scheduled), modified notes, outgoing links (broken-link detection), and backlinks — surfaces what happened, what's open, and what needs follow-up

Prompts adapt to your configuration (MEMORY_DIR, daily-notes settings) and work for any vault out of the box. Pass max_chars to cap embedded content if your client has payload limits.

Client support: Prompts work in Claude Desktop (Chat and Cowork — via the + menu under your connector), Claude Code (slash commands), and OpenCode. Support in other clients (Cursor, Windsurf) varies — see the MCP clients matrix for the latest.


Properties

Vault Cortex indexes every property in your notes, but five get promoted treatment — dedicated columns for fast filtering, and top-level fields in every search and discovery result:

Property

What you can do

title

Display name in search results; falls back to the filename when missing

tags

Search and filter by tag, including parent-child hierarchies (project matches project/vault-cortex)

type

Filter by note type — meeting, person, session-log, or any value your vault uses

created

Sort by creation date and see when each note was created alongside every search result

related

Filter for notes that cross-reference a specific link — surfaces connections invisible without a graph query

All other properties are still fully queryable — use vault_search with filters.properties for combined text + metadata queries, or vault_search_by_property for metadata-only lookups. vault_list_property_keys and vault_list_property_values discover what properties exist across your vault.

These are conventions, not requirements — Vault Cortex works with any property schema. Promoted properties give you richer filtering and cleaner results out of the box.

Leading callouts get the same treatment. When a note's first body content is an Obsidian callout (> [!type]) — either right after frontmatter or right after the title heading — it's indexed and surfaced alongside every discovery result (on vault_search, ask for it with include_leading_callout). This makes notes self-describing: an agent scanning results can see what each note is for before deciding which to read. The memory templates use > [!info] Scope of this file callouts for this, and any note in your vault can use the same pattern.


Configuration

All settings are environment variables with sensible defaults. Some defaults derive from other settings — the Default column shows each derivation, and a value you set replaces the whole derived default. Remote deployments also forward Obsidian Sync's own settings — DEVICE_NAME, SYNC_MODE, CONFLICT_STRATEGY, SYNC_CONFIGS, SYNC_EXCLUDED_FOLDERS, SYNC_FILE_TYPES — documented in the remote guide's configuration table.

Variable

Required?

Default

Description

MCP_AUTH_TOKEN

Yes

—

Bearer token for authentication (also the JWT signing key)

VAULT_PATH

Local only

—

Host path to your vault (bind mount source; remote uses a named volume). Must not contain *, ?, or [ — rejected at startup.

PUBLIC_URL

Remote only

—

Public URL for OAuth discovery metadata. Filled in automatically on Render and Railway (from RENDER_EXTERNAL_URL or RAILWAY_PUBLIC_DOMAIN) when left unset

OBSIDIAN_AUTH_TOKEN

—

—

Obsidian Sync auth token. Leave empty to sign in through the /setup page after deploy; or the CLI's get-sync-token captures it for you

VAULT_NAME

Remote only

—

Exact name of your Obsidian vault (case-sensitive)

VAULT_PASSWORD

Remote only

—

End-to-end encryption password, if your vault has one. Leave empty otherwise.

STORAGE_ROOT

—

—

One directory for everything that must persist — the vault, the search index, and Obsidian Sync state — for container hosting platforms that allow a single persistent volume (Railway, Render). Mount the volume there and set this to the same path. Must not contain *, ?, or [ — rejected at startup.

EMBEDDING_ENABLED

—

true

Set false to disable the embedding pipeline — skips model download, vector tables, embedding passes, and hybrid search. Search falls back to FTS5 keyword matching.

RERANK_MODE

—

blended

Cross-encoder reranking mode: blended applies position-aware score blending after RRF fusion (~200ms added latency), none skips reranking. Only takes effect when EMBEDDING_ENABLED is true.

MEMORY_ENABLED

—

true

Set false to fully disable the memory layer — hides memory tools, skips bootstrap, omits memory from server metadata. MEMORY_DIR still supplies the defaults for PROTECTED_PATHS and ORPHAN_EXCLUDE_FOLDERS when false.

FILE_TOOLS_ENABLED

—

true

Set false to hide file tools (vault_read_file, vault_list_files) — useful for remote deployments where Obsidian Sync has attachment syncing disabled.

READONLY_MODE

—

false

Set true to hide every tool that changes the vault and skip memory folder auto-creation — connected clients can read and search but never edit.

DISABLED_TOOLS

—

—

Hide individual tools by name, comma-separated (e.g. vault_delete_note,vault_move_note). Names match the Tool column in the tools table. Subtractive only — it cannot re-enable a tool another setting hides. An unknown tool name stops the server at startup, so typos surface immediately.

MEMORY_DIR

—

About Me

Vault folder for structured memory files

PROTECTED_PATHS

—

MEMORY_DIR, daily notes folder

Folders that vault_delete_note and vault_move_note refuse to touch. The default daily notes folder is read from DAILY_NOTES_FOLDER or .obsidian/daily-notes.json (default Daily Notes); see Daily notes for root and empty settings. Overrides the default entirely when set.

ORPHAN_EXCLUDE_FOLDERS

—

daily notes folder, Templates, MEMORY_DIR

Folders excluded from orphan detection. The daily notes folder is read from DAILY_NOTES_FOLDER or .obsidian/daily-notes.json (default Daily Notes); see Daily notes for root and empty settings. Set a list to replace all defaults, or ORPHAN_EXCLUDE_FOLDERS=, to exclude nothing. An empty value uses the defaults.

DAILY_NOTES_FOLDER

—

from daily-notes.json

Sets the folder your daily notes live in. When unset, read from .obsidian/daily-notes.json; Daily Notes when that file is missing or unreadable. See Daily notes.

DAILY_NOTES_FORMAT

—

from daily-notes.json

Sets the daily note filename format — same tokens as Obsidian's daily note date format setting. When unset, read from .obsidian/daily-notes.json, falling back to YYYY-MM-DD. See Daily notes.

TZ

—

UTC

IANA timezone for timestamps and daily note resolution

SERVICE_DOCUMENTATION_URL

—

GitHub repo URL

URL returned in OAuth discovery metadata

LOG_LEVEL

—

info

Logging verbosity: debug, info, warn, error

LOG_DIR

—

/data/logs (remote), $STORAGE_ROOT/data/logs (single-volume), none (local)

Directory for log files that survive container re-creation. The container's own log (what docker logs shows) is always written, but Docker discards it whenever the container is recreated — on image updates or config changes. Date-stamped files under LOG_DIR live on the data volume and survive. none keeps only the container log.

LOG_RETENTION_DAYS

—

90

Days to keep log files before automatic cleanup on startup; only applies when LOG_DIR is a path

WINDOWS_MODE

—

false

On Windows? Set true. Switches the file watcher to polling and note moves to rename-based writes so a vault on a C: drive works through Docker Desktop. Safe to leave on for any Windows setup; unneeded on macOS/Linux/WSL2.

MAX_FILE_BYTES

—

52428800 (50 MiB)

Maximum file size vault_read_file will read (in bytes). Files exceeding this are rejected before reading. Raise for vaults with very large individual files.

MAX_IMAGE_OUTPUT_BYTES

—

49152 (48 KiB)

Byte budget for images delivered by vault_read_file, in binary bytes before base64 encoding. Images exceeding this are downscaled and recompressed to fit. Sized for the tightest mainstream MCP client cap; raise for clients that accept larger responses.

MAX_PDF_RENDER_PAGES

—

5

Maximum PDF pages to render as images when raw: true is set on vault_read_file. The per-page byte budget is MAX_IMAGE_OUTPUT_BYTES divided evenly across the rendered pages — fewer pages means higher quality each.

TRASH_RETENTION_DAYS

Local only

30

Days a note deleted under Obsidian's default "Move to system trash" setting stays in .trash/ before the server cleans it up. Set none to keep those notes forever. Only notes the server itself moved there are cleaned up. With Obsidian Sync, deletes are permanent on the server and recoverable from Sync's version history.

TRUST_PROXY_HOPS

—

0

Number of trusted reverse-proxy hops used to derive the client IP from X-Forwarded-For (OAuth rate limiting, request logs). Set 1 when exactly one proxy you control sits in front of the server (Caddy, nginx, Cloudflare Tunnel, API Gateway). With 0, injected forwarding headers are ignored.

TRUST_FORWARDED_HOPS

—

0

How many trailing for= entries in the RFC 7239 Forwarded header belong to proxies you control. 0 ignores the header; 1 when the proxy in front writes it (e.g. AWS API Gateway); 2 when a CDN fronts that proxy and is the only way to reach it.

See templates/memory/ for memory file examples and the dated-entry design philosophy.

Daily notes

vault_get_daily_note and the daily-review prompt find your daily notes using the folder and filename date format configured in Obsidian, read from your vault's .obsidian/daily-notes.json:

  • Local mode reads the file straight from your bind-mounted vault — nothing to set up.

  • Remote mode receives it through Obsidian Sync's vault configuration syncing. The server pulls it by default (the SYNC_CONFIGS setting in .env), but your devices must also send it: turn on Obsidian Settings → Sync → Vault configuration sync on each device. Details: the remote guide's Daily notes section.

When the file isn't available — or if you use the Periodic Notes plugin, whose settings daily-notes.json doesn't hold — set the values yourself:

  • DAILY_NOTES_FOLDER — any vault-relative path: Journal, Planner/Daily, or / for the vault root

  • DAILY_NOTES_FORMAT — same tokens as Obsidian's date format setting: YYYY-MM-DD-dddd, YYYY/MM/DD, MMM D, YYYY, …

You can set one or both — a set value always wins over the config file. A value you leave unset comes from daily-notes.json; when that file is missing or unreadable, the server uses Daily Notes and YYYY-MM-DD.

  • Changes in Obsidian apply on the next call when .obsidian/daily-notes.json is available in the server's vault.

  • Apply environment changes with the CLI's restart command, recreate the Compose container, or redeploy on your hosting platform.

  • If you leave the folder empty in Obsidian's Daily notes options, the server follows Obsidian's Default location for new notes setting (Settings → Files and links), read from .obsidian/app.json. Same folder as current file counts as the vault root, because Obsidian creates a daily note without reference to an open note and so has no folder to follow. When app.json isn't in the server's vault, as in remote mode until Main settings is synced, the server uses the vault root.

  • If your daily notes live in the vault root, or in the new-note folder because the folder setting is empty, delete and move don't protect them and orphan results can include them. Protecting or excluding that folder would also cover every other note in it.

Note: A few date format tokens are unsupported — ordinals (Do, Mo, DDDo, wo), dd (2-letter weekday), d (weekday number), e, k/kk, w (week number), Q (quarter), Z/ZZ (UTC offset), and the localized formats (L–LLLL, LT, LTS). The server can't reproduce the filenames Obsidian creates with these tokens, so it could never find the notes. If your format uses any of them, vault_get_daily_note returns a clear error — change the format in Obsidian or set DAILY_NOTES_FORMAT to a supported alternative.


Data Integrity

Vault Cortex writes to personal notes — the file safety layer is built to prevent corruption, not just errors.

  • Atomic writes — every file write stages to a temp file, then renames. Readers never see a partial or 0-byte note. Exclusive creates use link() (POSIX no-clobber) to close the TOCTOU window on note moves.

  • Per-file mutex — concurrent MCP tool calls serialize or fail-fast per file. Moves lock the source, destination, and every backlink source as one unit.

  • Body edits keep properties as written — editing a note's body leaves its properties (the --- block Obsidian shows as Properties) as they were, comments included.

  • Property rewrites refuse what they can't keep — vault_update_properties, a vault_write_note overwrite, the task tools and a move that changes a link inside the block rewrite the block: every property and value stays, YAML comments are dropped, and the formatting may change. A rewrite is refused, leaving the note unchanged, when the block is invalid YAML, is a list or a single value instead of key: value properties, or holds a value with an explicit tag such as !!str; the error says how to repair it.

  • Notes with broken properties stay usable — search treats a note whose properties are invalid YAML as having none, as Obsidian does. The note still shows in search and task lists, its body links still count as backlinks, and you can read, edit and move it with the block left as written. The server log flags it for repair.

  • Path traversal blocked — resolveSafePath() resolves then prefix-checks every path. Protected-path deletion is refused after normalization. Memory file names reject separators at the boundary.

  • Hidden paths are off-limits — files and folders starting with a dot (.obsidian/, .trash/) never appear in listings or search, and any tool call that targets one directly is rejected, matching Obsidian. Plugin configs and their API keys stay out of reach.

  • Deletes honor Obsidian's trash setting — with "Deleted files" at Obsidian's default "Move to system trash" or at "Move to Obsidian trash", a deleted note moves to .trash/ inside the vault instead of being removed (a container has no system trash; .trash/ is Obsidian's own fallback for that). "Permanently delete" removes the note for good.

  • Obsidian Sync deployments delete permanently — the delete syncs to every device, and recovery is Sync's version history rather than a trash folder.

  • Bounded trash with a retention sweep — notes the server moves to .trash/ under the system-default setting are cleaned up after TRASH_RETENTION_DAYS (default 30 days; none keeps them forever). The sweep removes only files it recorded — notes Obsidian itself trashed, and "Move to Obsidian trash" deletes, are never touched.

  • Injection prevention — search queries are parameterized and FTS5-sanitized; prompt content is wrapped in XML data markers with closing-tag escaping to prevent tag-breakout injection.

  • Container hardening — non-root user, PID 1 init, no package managers in the runtime image, digest-pinned base, graceful shutdown.

  • Read-only mode — READONLY_MODE=true hides every tool that edits the vault, so a connected client can read and search but never change a note.

See ARCHITECTURE.md → Data Integrity for mechanism details and SECURITY.md → Runtime Hardening for how each part of the server is hardened.


Authentication

For a server with read/write access to personal notes, authentication is not optional. Vault Cortex implements the full OAuth 2.1 specification, including PKCE and refresh-token rotation. The AWS (SST) deployment adds defense-in-depth: requests are validated at two independent layers (API Gateway Lambda authorizer + Express middleware). Per BlueRock's 2026 MCP security analysis, only 8.5% of MCP servers implement OAuth; 41% have no authentication at all.

Two methods:

Method

Used by

Token format

OAuth 2.1

Claude Desktop, Claude Code, claude.ai, any OAuth client

JWT (HS256, 6h)

Static bearer

Claude Code, MCP Inspector, curl

Raw MCP_AUTH_TOKEN

The method follows from your client — OAuth when it supports it, the raw token in a header otherwise (Connect your MCP client shows both).

OAuth uses dynamic client registration — no manual Client ID or Secret needed:

  1. Your client registers automatically and receives a client ID and secret.

  2. Enter your MCP_AUTH_TOKEN on the browser consent page to approve access.

  3. Your client includes the issued secret in subsequent token requests automatically.

Refresh tokens have a 60-day sliding expiry. Access tokens are bound to your server's URL, so a token minted for one deployment is never accepted by another. Rotating MCP_AUTH_TOKEN ends every session — each client re-authorizes through the consent page.

See ARCHITECTURE.md → Auth for the full flow diagram.


Deployment Options

Local runs on your machine. Remote deployments run on a VPS or a hosted container platform — your vault is accessible even when your laptop is closed.

Whichever path you pick, the server is replaceable and your vault isn't. Your notes are plain Markdown files, synced by Obsidian to every device you own; the container holds a copy and an index it can rebuild from scratch. Shut down the VPS, delete the Render or Railway service, switch hosts — the same files are still on your machine and in Obsidian Sync, readable by anything. That's the difference from an AI notebook whose real home is the vendor's database: here the host is a convenience, not a custodian.

Path

What

Guide

Local

Your vault on your machine — free, no cloud

deploy/local/

Remote · one-click

Render or Railway — one persistent volume, no server to manage

deploy/render/ · deploy/railway/

Remote · self-hosted

VPS + Obsidian Sync — access from any device

deploy/remote/

Remote · AWS (SST)

IaC reference deployment — automated infra, defense-in-depth auth

DEPLOY.md

The AWS path includes CI/CD workflows built for this repo — forkers need to configure their own credentials and stage before deploying.

Every path runs the same image, ghcr.io/aliasunder/vault-cortex — :latest is the MCP server alone (local), :remote bundles Obsidian Sync in the same container under s6-overlay supervision (one-click, self-hosted, and AWS). One container means any OCI runtime works: docker run, Podman, nerdctl — Docker Compose is optional.

Also on Docker Hub: the same images are mirrored to aliasunder/vault-cortex. GHCR is the primary source; Hub tags are identical.

Cost: A remote setup needs a VPS or a hosted platform plan, plus $4 USD/mo for Obsidian Sync. A 2 GiB instance handles semantic search fine for a typical vault; 4 GiB adds headroom for concurrent search and larger vaults. Skip semantic search entirely to go smaller still. Local-only is free. The reference AWS deployment runs ~$17–29 USD/mo all-in.

More than one vault? Each vault runs as its own install, with its own container, data and connector. On your own machine or server, give each install its own name: see Run more than one vault in the local and remote guides. On Render or Railway, deploy a separate service for each vault.

One-click deploy

Buttons and prerequisites are in Quick Start → Remote. Each guide walks through the deploy, where to find your URL and token, how to update, and how to delete: deploy/render/ (from the render.yaml Blueprint at the repo root) · deploy/railway/ (from a published template).

Community deployments

Deployment templates built and maintained by the community — not tested here, and they may lag behind releases.

  • vault-cortex-aca — Bicep template for Azure Container Apps by @flytzen. Runs the :remote image behind Container Apps ingress with free managed HTTPS; storage is deliberately ephemeral, with Obsidian Sync as the source of truth.

Built a deployment for another platform? Open a PR to add it here.


Development

# Run locally with hot reload
PUBLIC_URL=http://localhost:8000 MCP_AUTH_TOKEN=local-dev-token VAULT_PATH=~/Vault npm run dev:mcp

# Tests
npm test

# Full check suite
npm run prettier:check && npm run lint && npm run markdownlint && npm run lint:shell && npm run knip && npm test && npm run build

npm test includes integration tests that boot a real server and call every tool and prompt over HTTP — verifying auth enforcement, config-gated tool surfaces, write mutation integrity (each write is read back), and boot rejection on misconfiguration. See SECURITY.md for the security-relevant coverage.

MCP Inspector — interactive browser UI for testing tools:

# Start server (terminal 1), then:
npx @modelcontextprotocol/inspector
# Enter http://localhost:8000/mcp as URL, local-dev-token as Bearer token

See CONTRIBUTING.md for the full development setup.


Companion: obsidian-vault skill

The MCP server works on its own with any client. For agents that support skills (Claude Code, Cursor, Windsurf, Cline, and 70+ others), the obsidian-vault skill adds deeper knowledge of Obsidian-flavored markdown — frontmatter conventions, callout syntax, and plugin-specific formats like Dataview, Tasks, and Kanban.

npx skills add aliasunder/agent-skills --skill obsidian-vault

Skill source →


Roadmap

Planned work, what's being explored, and explicit non-goals live in ROADMAP.md.


Acknowledgments

Obsidian sync is powered by obsidian-headless — containerization approach inspired by @Belphemur's obsidian-headless-sync-docker. The :remote image's s6-overlay supervision scaffolding was absorbed from that project's maintained fork and now lives in this repo.

The hybrid search pipeline draws on patterns from @tobi's qmd — RRF fusion with rank bonuses, position-aware score blending for cross-encoder reranking, content-hash gating, and heading-aware chunking.

Contributing

See CONTRIBUTING.md for development setup, code conventions, and PR guidelines.

License

MIT

The :remote image bundles obsidian-headless (the ob CLI), which is proprietary — its package.json declares "license": "UNLICENSED" (© Dynalist Inc. / Obsidian). It is installed from public npm at build time; the MIT license here does not cover it, and using it requires an active Obsidian Sync subscription. The :latest (local) image contains no proprietary components.

Security

Report vulnerabilities privately — see SECURITY.md.

Available Tools

33 tools
vault_create_taskCreate 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. Override format only at the user's request, since the Tasks 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 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, 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: 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 Tasks 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.

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, 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" / "no checkbox symbol for status ..." — in the Tasks plugin's status settings, the parent's checkbox character (c) is typed NON_TASK so it is not a task, or todo has no symbol and the default [ ] character has another type; the user must change that character's type or add a todo symbol there, then restart the server

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

  • "blockId ... already exists in this note" / "blockId ... contains invalid characters" — block_id must be unique in the note and 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" / "description must be a single line" / "subtasks items must be a single line" — pass visible text on a single line

  • "taskId ... contains invalid characters" / "dependsOn entry ... contains invalid characters" — task_id and every depends_on entry must match [a-zA-Z0-9_-]+

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

  • "invalid date: …" — a date param isn't in YYYY-MM-DD form or names a day that doesn't exist; pass a real YYYY-MM-DD date

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

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

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

  • "the note would open with a properties block …" — 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: 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. The write still succeeds either way; reword the text to clear any advisories the result carries.

Returns: JSON { path, line, description, block_id, heading, subtasks, changes, advisories } — line is the new task's 1-based line number in the file; 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.

NameRequiredDescriptionDefault
dueNoDeadline (📅), YYYY-MM-DD, calendar-validated. Omit when there is no deadline.
pathYesVault-relative path to the note (must end in ".md"). The note must already exist. Use the exact letter case.
startNoEarliest day work can begin (🛫), YYYY-MM-DD, calendar-validated.
formatNoField format. Default: auto-detected from .obsidian/ config, falling back to emoji.
headingNoTarget heading's text, without the # marks, matched exactly. On a regular note, omit to append at end of body.
task_idNoTasks plugin 🆔 identifier other tasks can name in depends_on — letters, digits, hyphens, and underscores; separate from block_id.
block_idNoThe ^block-id (without the ^) for stable identification — letters, digits, and hyphens only ([a-zA-Z0-9-]+), unique within the note.
positionNoWhere within the heading section the task is placed: "top" first in the section, "bottom" at the end of the section, or a 1-based integer for an exact slot among the section's top-level tasks (a Kanban lane's cards; nested tasks are not counted). A position past the count lands directly below the last top-level task (unlike "bottom", which appends after any non-task text at the end of the section). Defaults to "bottom" ("top" on Kanban boards set to new-card-insertion-method: "prepend").
priorityNoPriority signifier: highest 🔺, high ⏫, medium 🔼, low 🔽, lowest ⏬. Omit for normal priority — no signifier is written.
subtasksNoChecklist sub-items: text-only todo lines indented under the card, with no metadata (not even ➕). For a sub-task with dates or priority, make a separate call with parent_block_id.
scheduledNoDay the work is planned for (⏳), YYYY-MM-DD, calendar-validated.
depends_onNoTasks plugin ⛔ dependency IDs (🆔 values of other tasks), not checked against existing tasks. Non-empty; omit when there are no dependencies.
recurrenceNoTasks plugin 🔁 rule in natural language (e.g. "every week", "every month on the 15th", "every 2 weeks when done"). Completing the task spawns its next occurrence.
descriptionYesThe task text (before metadata fields).
parent_lineNo1-based line number of an existing task to nest under as a sub-task. Fragile if the file changed since the line was read.
on_completionNoTasks plugin 🏁 onCompletion action. "delete" removes the task line and its children on completion; "keep" leaves it in place, as omitting the field does.
parent_block_idNo^block-id (without the ^) of an existing task to nest under as a sub-task.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare write/non-idempotent/non-destructive; the description adds real behavioral context: the task is always created as todo with created date set to today, format is auto-detected and should only be overridden on request, concurrent writes are rejected, and rewrites must preserve the properties block. It also documents the non-obvious parsing behavior where trailing emoji/Dataview fields get read back as metadata, surfaced via advisories.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well front-loaded: purpose, then four targeted examples, then when-to-use, then parameter rules, then errors, then return shape. The error catalog is long (over twenty bullets), but nearly all entries tell the agent how to recover; a few edge-case messages (properties block, ENAMETOOLONG) are marginal against a 17-parameter tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Seventeen parameters with no output schema would normally leave gaps, but the description supplies an explicit Returns section (path, line, block_id, heading, subtasks, changes, advisories) and explains how line acts as the follow-up handle when block_id is absent. Error remediation and Obsidian parsing caveats round out the coverage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds cross-parameter constraints the schema cannot express: heading is required on Kanban boards unless a parent is given, parent_block_id/parent_line are mutually exclusive with each other and with heading, block_id is refused on Kanban sub-tasks, and position is ignored without a heading or under a parent. Some content (block_id regex, position enum semantics, priority enum) restates the schema, which keeps it from a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence gives a specific verb and resource ("Create a correctly-formatted task") plus the scope of what gets written. It distinguishes itself from siblings by naming vault_update_task for status changes and checklist items, so an agent can choose without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is an explicit "When to use" section that states the condition (creating a new task card on a board or in a note) and names the alternative (vault_update_task's add_subtasks for lightweight checklist items). It also points to vault_list_tasks for round-trip verification and vault_list_notes for path checking.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_delete_memoryDelete Memory EntryA
Destructive

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

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

When to use: Removing an entry that was wrong when it was written — 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 pick the bullet line within the given section; identical copies make the match ambiguous.

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

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" / "memory file must be named without .md" — pass the file's bare name: no folder or slash, no leading dot, and no .md.

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

  • "section not found: …" — no H2 heading matches; the 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. 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.

  • "cannot read memory file …" — the file can't be read: check file against vault_list_memory_files (a name longer than the filesystem allows fails here); otherwise its permissions need the vault's owner

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

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

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

NameRequiredDescriptionDefault
dateYesISO YYYY-MM-DD date of the entry (e.g. "2026-05-01").
fileYesMemory file name without .md (e.g. "Principles"). Use the exact letter case.
entryYesExact entry text, without the "- **YYYY-MM-DD**: " prefix or bullet.
sectionYesH2 section heading containing the entry. Matched case-insensitively, with or without the "(newest first)" suffix.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the destructive annotation, the description discloses append-only default behavior, the living-file exception, ambiguity around identical entries, safety-guard shrink protection, and a detailed error catalog. It clearly explains failure modes and the expected return value.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with a clear purpose sentence and examples, followed by structured usage, parameter, error, and return sections. It is somewhat long due to the error catalog, but that detail is justified for a destructive operation with tricky matching.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive four-parameter memory-deletion tool with no output schema, the description covers purpose, prerequisites, alternatives, matching rules, error behavior, and return format. An agent should have everything needed to invoke it correctly or recover from failure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds non-obvious matching semantics: date and entry jointly select a bullet, identical copies make the match ambiguous, and section scoping means the same entry under another heading is not found. It also clarifies file naming through error messages.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb, resource, and scope: 'Delete a single dated entry from a memory file in About Me/'. It also explicitly distinguishes this operation from vault_update_memory, vault_delete_note, and vault_delete_span in context.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit 'When to use' guidance for removing entries that were wrong when written, warns not to delete merely to reflect a change, names vault_update_memory as the alternative, and carves out an entry-policy: living exception. It also tells the agent to call vault_get_memory first and to prefer vault_delete_note for entire notes.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_delete_noteDelete NoteA
Destructive

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

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

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

Behavior:

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

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

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

    • "Permanently delete" removes the note for good.

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

Parameters:

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

Errors:

  • "cannot delete protected path" — the path sits under a protected folder; use vault_delete_memory for 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

  • "cannot move to trash …" without "100 collisions" — 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) — the note stays put; ask the vault owner to fix the cause (e.g. permissions), then retry

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

  • "cannot read trash config from .obsidian/app.json" — the file exists but can't be read or parsed; 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; 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.

NameRequiredDescriptionDefault
pathYesVault-relative path of the note to delete, including the ".md" extension. Use the exact letter case.
prune_empty_foldersNoWhen true, also remove parent folders the delete leaves empty. Default false.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag destructiveHint=true, but the description goes well beyond: the caller cannot choose or see the trash outcome in advance, retention semantics (TRASH_RETENTION_DAYS 30 by default, never when set to none), the fact that backlinks break, protected-path refusal rules, and per-error recovery guidance. This is exactly the extra context annotations cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the one-liner, examples, and when-to-use before the long behavior and error sections, and the section headers make it scannable. The error catalogue is unusually long for a two-parameter tool, and a few entries (config-read failures) could be compressed without losing actionable information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, and the description supplies the return message format and the difference between "Deleted" and "Moved … to trash". Combined with the destructive-annotation baseline, permission/config failure modes, and protected-path rules, an agent has everything needed to call this correctly and interpret the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning for prune_empty_folders: a folder holding any file (even a hidden .DS_Store) survives, and pruning is best-effort so an un-removable folder never fails the call. The path parameter gets less added value beyond the extension and hidden-path errors.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Delete a markdown note") and immediately clarifies the two possible outcomes. It distinguishes itself from siblings by name, routing memory-entry removal to vault_delete_memory, relocation to vault_move_note, and replacement to vault_write_note.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit "When to use" plus three named alternative tools with the condition that selects each. Also gives two worked examples, one of which exercises the optional parameter, so the agent knows exactly which call shape to pick.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_delete_spanDelete SpanA
Destructive

Delete a contiguous block of whole lines from a note's body by referencing short anchor substrings instead of reproducing the full block text. Properties are kept as written, and CRLF line breaks are saved as LF.

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

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

Parameters:

  • end_anchor is searched at or after the start line and 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 (```): the end anchor is ambiguous, and first_match: true would end the span at the opening fence; to remove such a block, use vault_replace_in_note with new_text: "".

Behavior: Blank lines never double: the text above and below the removed lines ends up separated by the wider of the two runs of empty lines that stood there; at the note's end the run above counts one fewer ("A\n\nB\n" without "B" → "A\n"). So removing a table row leaves the table whole. Empty lines elsewhere in the note stay as they are, and a line holding only spaces or tabs counts as text, not as an empty line.

Errors:

  • "note not found" — verify path with vault_list_notes

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

  • "start anchor not found" / "end anchor not found" — no 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: …", "EACCES: …" or another filesystem error code — the message says what to do

  • "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: 'Deleted line(s) from : ""' — the removed lines joined by line breaks, cut at 80 characters with "…".

NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "Tracker.md", "Notes/Plan.md"). Use the exact letter case.
end_anchorNoShort substring that identifies the LAST line of the block (case-sensitive). Omit to delete just the single line containing start_anchor.
first_matchNoIf an anchor matches more than one line, delete using the first match instead of erroring (default: false — ambiguity is an error).
start_anchorYesShort, unique substring that identifies the first line of the block (case-sensitive). Pick a brief fragment — do not paste the whole block.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark this as destructive and non-idempotent, and the description adds substantial behavioral context: properties are preserved, CRLF becomes LF, blank-line normalization is described in detail, anchor ambiguity is explained, and error conditions are enumerated. Nothing contradicts the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-organized into examples, usage guidance, parameter notes, behavior, errors, and returns. The length is largely justified for a destructive editor with subtle whitespace and anchor semantics, though the error catalogue is somewhat expansive.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive mutation tool with no output schema, the description covers the operation, alternatives, edge cases, behavioral side effects, error handling, and return format. An agent has enough information to invoke it correctly without guessing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so parameter basics are already documented. The description still adds real meaning beyond the schema by explaining that end_anchor is searched at or after the start line, must be unique among those lines, that co-located anchors delete only one line, and how first_match resolves ambiguity.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: deleting a contiguous block of whole lines from a note's body using anchor substrings. It clearly distinguishes the operation from vault_replace_in_note and vault_replace_span, and the examples make the intended granularity concrete.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to use it — removing a block already read where reproducing old_text would be error-prone — and names alternatives: vault_replace_in_note for small in-place edits and vault_replace_span for atomic replacement. It also includes the specific code-fence edge case where this tool should not be used.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_find_orphansFind OrphansA
Read-onlyIdempotent

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.

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

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

Parameters:

  • exclude_folders replaces the defaults, it does not add to them — list a default yourself to keep it. Pass [] for no exclusions. Each entry names a whole folder, subfolders included ("Projects" also excludes "Projects/Archive" but not "ProjectsOld/"), ignoring ASCII letter case. Excluded notes are left out of the results; links from them still count as incoming links. The defaults' daily notes folder is the one vault_get_daily_note's path starts with; when it is the vault root or blank, the defaults hold no daily folder.

  • 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.

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

Returns: JSON array of note metadata (path, title, tags, related, folder (top-level folder), type, created, modified, bytes, leading_callout, additional_properties), sorted by most recently modified. The bytes field is the on-disk file size. A leading_callout field appears only when the note has one; additional_properties only when frontmatter has keys outside title, type, created, related, and tags.

NameRequiredDescriptionDefault
limitNoMax results (default 50)
exclude_foldersNoFolder paths to exclude (e.g. Projects; default: the daily notes folder, "Templates", "About Me")

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish a safe read-only, idempotent operation, and the description adds substantial behavioral context: exclusion defaults, how excluded folders affect results versus links, limit truncation ambiguity, empty-result meaning, and specific error conditions. There is no contradiction with the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with purpose, then examples, usage guidance, parameter caveats, errors, and return shape. Despite its length, every section carries non-obvious operational value and there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter read tool with no output schema, the description fully explains return structure, optional fields, sorting, error behavior, and edge cases. An agent has everything needed to call it correctly and interpret the results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, but the description adds meaning beyond the schema: exclude_folders replaces rather than extends defaults, [] means no exclusions, folder exclusion includes subfolders and is case-insensitive, and excluded notes still contribute incoming links. It also explains limit applies after exclusions and sorting, and that truncation is not signaled.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb and resource ('Find notes with no incoming links') and defines orphan status precisely, including the self-link edge case. It clearly distinguishes this scan from the sibling vault_get_backlinks, which checks connectivity for one note.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states the use case (vault maintenance, surfacing notes to integrate into the graph), names the follow-up action with vault_patch_note, and tells the agent when to prefer vault_get_backlinks instead. The routing is unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_get_daily_noteGet Daily NoteA
Read-onlyIdempotent

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

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

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

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.

NameRequiredDescriptionDefault
dateNoYYYY-MM-DD (e.g. "2026-05-13", "2025-12-31"). Defaults to today in the server's timezone.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the read-only/idempotent profile, but the description goes far beyond them: it explains the full config resolution order, documents each distinct error string and its remediation, and clarifies that a missing note returns exists=false rather than failing.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and examples, then when-to-use, parameters, errors, and returns in clear labeled blocks. The error catalogue is long but each entry maps to a distinct failure mode with an action, so it earns its space; slightly verbose overall.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, yet the description fully specifies the return shape (path, content, exists) and even prescribes the follow-up action when the note is absent. Combined with the error coverage, an agent has everything needed to call and handle results correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and already documents the YYYY-MM-DD format and today-default, so baseline is 3. The description adds genuine value by declaring that both past and future dates are valid, removing ambiguity the schema does not address.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (read a daily note by date) and immediately explains the resolution chain for folder and filename format via env vars, .obsidian config, then defaults. This is clearly distinguishable from vault_read_note and vault_recent_notes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'When to use' section names the exact alternative for each adjacent need: vault_patch_note for appending to a section, vault_recent_notes for general recent activity, and notes that recent_notes is not date-filtered. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_get_memoryGet MemoryA
Read-onlyIdempotent

Read semantic memory from About Me/ files. These are structured memory files containing dated bullet entries organized under H2 headings. With file: that file's content, frontmatter stripped. With file+section: just that H2 section's entries. No args: every file's content, frontmatter stripped, joined by --- lines — 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, their default position). 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.

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

When to use: Reading user preferences, principles, opinions, or other persistent context stored in About Me/ files. Call vault_list_memory_files first to discover valid file and section names. Use on_or_after when you need entries from a known date forward. 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" / "memory file must be named without .md" — pass the file's bare name: no folder or slash, no leading dot, and no .md

  • "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

  • "cannot read memory file …" / "cannot list memory folder …" — a memory file, or the About Me/ folder itself on a call without file, can't be read: check file against vault_list_memory_files (a name longer than the filesystem allows fails here); otherwise ask the vault's owner to check the folder

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). An empty match returns { entries: [], total: 0, on_or_after }.

NameRequiredDescriptionDefault
fileNoMemory file name without .md (e.g. "Principles", "Opinions"). Use the exact letter case.
sectionNoH2 section heading (e.g. "Decision heuristics (newest first)"). Matched case-insensitively, with or without the "(newest first)" suffix. Omit to read the whole file.
on_or_afterNoInclusive date filter (YYYY-MM-DD). Requires a file.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, so the description rightly spends its space on what annotations can't say: that the no-arg call 'can be large', that a missing setup returns an empty string, that ordering follows document order with newest-first convention, and a full enumerated error catalog with remediation for each. That is unusually rich behavioral disclosure for a read tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose, then modes, examples, usage, errors, and returns in a scannable order. It runs long and the trailing 'Returns:' paragraph partially repeats the return-format information already stated in the mode descriptions (frontmatter stripping, JSON shape), which is the main waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description must carry the return contract — and it does, distinguishing raw markdown from the structured { entries, total, on_or_after } shape with per-entry fields and the empty-match case. Combined with the error catalog and discovery prerequisite, an agent has everything needed to call this correctly on the first attempt.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so individual parameter meanings are already documented; the description's added value is the cross-parameter contract — that section and on_or_after both require file, that omitting section reads all sections while keeping section attribution, and that on_or_after switches the return format to JSON. Worked examples reinforce the combinations. It stops short of a true 5 only because the per-parameter detail largely restates the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence names the specific verb and resource (read semantic memory from About Me/ files) and immediately characterizes the data format (dated bullet entries under H2 headings). It then enumerates the three calling modes (no args, file, file+section, file+on_or_after) so an agent knows exactly what each shape yields, and it is clearly distinct from siblings like vault_read_note and vault_memory_recall.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

An explicit 'When to use' block names the target content (preferences, principles, opinions, persistent context), prescribes a prerequisite call (vault_list_memory_files to discover valid file and section names), and gives a sibling exclusion ('Prefer vault_read_note for reading non-memory notes'). It also states when to reach for on_or_after versus a plain read.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_insert_at_anchorInsert at AnchorA

Insert content as whole lines into a note's body, before or after a specific line identified by a short anchor substring. Properties are kept as written, and CRLF line breaks are saved as LF.

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:

  • 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

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

  • "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 line(s) <before|after> anchor in " — N counts the lines in content as sent (its line breaks plus one).

NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "Notes/Plan.md", "Tracker.md"). Use the exact letter case.
anchorYesShort, unique substring on the line to insert next to (case-sensitive). Pick a brief fragment — do not paste the whole line.
contentYesContent to insert (one or more lines).
positionYes"before" places the content on the lines above the anchor line; "after" places it on the lines below. The anchor line itself is never changed.
first_matchNoIf the anchor matches more than one line, use the first match instead of erroring (default: false — ambiguity is an error).

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare a non-destructive but non-idempotent write; the description goes well beyond that with CRLF→LF normalization, verbatim content preservation, trailing-newline semantics, path-safety blocking, concurrent-write behavior, and a full error catalog with remedies. Minor gap: it never says whether the write is atomic or what happens on partial failure, but the operational detail is unusually rich for a write tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then examples, then when-to-use, parameters, errors, and returns — a sensible ordering. The error list is long, but each entry maps an error string to a remediation action, so it is dense rather than padded; a slightly tighter grouping of the path-safety errors would push this to a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description supplies the return format verbatim ('Inserted <N> line(s) <before|after> anchor in <path>') plus the line-count convention. Combined with examples, error-to-remedy mapping, and content-formatting rules, an agent has everything needed to call this correctly on the first attempt.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description earns a bump by explaining content semantics (blank lines preserved, trailing newline adds a blank line), the anchor/first_match interplay in the ambiguity error, and two worked examples that show realistic argument combinations. The control-character and Obsidian-syntax notes add meaning the schema lacks.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

First sentence states a specific verb (insert), resource (note body), mechanism (whole lines relative to an anchor substring), and scope constraint (properties untouched). It is immediately distinguishable from vault_patch_note and vault_replace_span, both of which are named later.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'When to use' section names concrete scenarios (table rows, list items, landmark lines), and two named alternatives are routed away from: vault_patch_note for heading-targeted inserts and vault_replace_span for block replacement. When-not guidance is stated directly, not implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_list_filesList FilesA
Read-onlyIdempotent

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

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

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

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

Errors:

  • A visible folder containing no files — or one that doesn't exist — 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); use a vault-relative folder outside hidden folders, and omit folder to list the whole vault.

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

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

Reading listed files: vault_read_file reads images (.png/.jpg/.jpeg/.gif/.webp), .canvas, .pdf, and the text formats .svg/.json/.txt/.csv/.xml/.log/.yaml/.yml/.base; other listed types are not readable. The bytes value is the on-disk size, not the delivery cost. An image comes back shrunk to fit when needed, so a large listed image is still cheap to read. A text format returns verbatim, and one over 100 KiB must be read in windows with vault_read_file's start_line and limit.

NameRequiredDescriptionDefault
limitNoMax entries returned (default 50).
folderNoFolder path to search recursively (e.g. "attachments" or "Projects/media"). Omit to list the whole vault.
extensionsNoOnly include these extensions, case-insensitive, leading dot optional (e.g. [".png", "jpg"]).

TDQS

A4.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive and closed-world, so safety is covered. The description adds genuinely useful behavior: exact-case folder matching, hidden dot-file exclusion, empty-listing-instead-of-error, and truncation via limit. That is meaningfully beyond the annotations, though the long 'Reading listed files' tail drifts into vault_read_file's behavior rather than this tool's.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded and the sections (examples, when-to-use, behavior, errors, returns) are scannable. The final paragraph on how to read the listed files is useful but the longest and least about this tool, slightly inflating length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description fully documents the return shape (files with path/extension/bytes, extension_counts, total, truncated), plus error cases and edge behaviors. An agent has everything needed to call it and interpret the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds working examples for each parameter combination and clarifies that omitting folder lists the whole vault and that extensions are matched case-insensitively by the tool. The case-sensitivity caveat on folder is a real semantic addition not present in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List non-markdown files in the vault or a folder — images, canvases, PDFs, data files') plus what is returned (byte sizes, per-extension counts). It explicitly separates itself from vault_list_notes and vault_search, so an agent can distinguish it from siblings without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides an explicit 'When to use' section naming the downstream action (read with vault_read_file) and the alternatives that cover markdown notes (vault_list_notes, vault_search_by_folder, vault_search) and linked files (vault_get_outgoing_links). The routing logic is unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_list_memory_filesList Memory FilesA
Read-onlyIdempotent

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 and names ending in .md twice are skipped.

Errors:

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

  • "cannot list memory folder …" — a file sits where the memory folder belongs, or its permissions need the vault's owner

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

Returns: JSON array of file outlines, each { file, title, bytes, entry_policy, leading_callout, headings } — file is the name the other memory tools take as file (no .md); 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.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only, idempotent, closed-world, so the description correctly spends its budget on behavior beyond that: sort order, subfolder/hidden-file exclusion, empty-folder behavior, and two concrete error strings with retry/escalation advice. It also discloses a non-obvious truth — that entry_policy is a convention the server does not enforce — which no structured field conveys.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Strongly front-loaded: purpose, then example, then when-to-use, behavior, errors, returns — an agent can stop after two lines. Slight redundancy between the worked example and the 'Returns' block, which restates the same field list, costs it a point.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description must carry the return shape, and it does so field by field ({ file, title, bytes, entry_policy, leading_callout, headings }) including the null convention and entry_count placement on H2s. Combined with the error appendix, nothing an agent needs to call or interpret this tool is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero parameters, so there is no parameter semantics to explain and the baseline of 4 applies. The description instead devotes that space to the return shape, including that the `file` value is what other memory tools accept without the .md suffix — useful, but that is return-value information rather than parameter guidance.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('lists About Me/ memory files') and immediately delimits scope with 'Does NOT return actual entries', which is exactly the distinction an agent needs versus vault_get_memory. It also names sibling tools it precedes, so it is distinguishable without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

A dedicated 'When to use' section gives explicit ordering ('BEFORE calling vault_get_memory, vault_update_memory, or vault_delete_memory') and a mandatory call path ('Always call this first to get valid file and section names'). It also names the pruning case where entry_policy must be checked, covering the when-not implicitly.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_list_notesList NotesA
Read-onlyIdempotent

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/". 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 every note under it.

Behavior: Paths come back sorted by vault-relative path in code-unit order (uppercase before lowercase). Hidden (dot-prefixed) notes and folders are never listed; 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.

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

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

NameRequiredDescriptionDefault
globNoGlob pattern for note paths (e.g. "**/*session-log*.md").
folderNoVault-relative folder to list (e.g. "About Me", "Projects").

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnly, idempotent, non-destructive), yet the description adds sort order, hidden-file exclusion, symlink inclusion, empty-array-not-error semantics, and the exact error strings an agent may encounter plus recovery guidance. None of that is derivable from the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core contract, then examples, routing guidance, parameter notes, behavior, errors, and returns in a scannable hierarchy. Length is driven by genuinely non-obvious semantics (case sensitivity, glob scoping, error strings), and the mild restatement of the return type is harmless.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, but the 'Returns' line specifies the exact shape (JSON array of vault-relative path strings), and error conditions and their remediation are all covered. Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description substantially enriches it: folder recurses into subfolders unless a glob is given, exact letter case matters on case-sensitive filesystems, and glob is matched against the path inside folder with '*' vs '**' depth semantics spelled out.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (list), resource (.md file paths in the vault), and explicit scope limits ('Returns paths only — not content or metadata'), which immediately separates it from content/metadata searching siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

A dedicated 'When to use' line names the positive case (browsing by filename, matching path patterns) and explicitly routes to three alternatives: vault_search_by_folder for metadata, vault_search for content, vault_read_note for reading. That is when/when-not/alternatives in full.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_list_property_keysList Property KeysA
Read-onlyIdempotent

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"] }, ...] Example: vault_list_property_keys({ folder: "Projects" }) — the keys used under Projects/ only

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

Parameters:

  • folder names a whole folder 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, counting each array element separately; a checkbox appears as "1" or "0", and null values are skipped.

Errors:

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

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

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

NameRequiredDescriptionDefault
folderNoRestrict to a folder (e.g. "Projects")

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnly, idempotent, non-destructive), but the description goes well beyond with frontmatter-only scope (inline Dataview fields excluded), precise count semantics (null-valued notes included), sample_values computation rules (3 most frequent, array elements counted separately, booleans rendered "1"/"0", nulls skipped), and sort order. Error behavior is disclosed too: empty vault/folder returns [] rather than throwing, and SqliteError is attributed to the index rather than the call.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose, then cleanly sectioned into examples, when-to-use, parameters, behavior, errors, and returns. Two worked examples are arguably one more than strictly needed, but each sentence carries concrete information rather than filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With one optional parameter and no output schema, the description supplies the return shape inline (JSON array of {key, count, sample_values}), the sort order, and the empty-result case. Nothing an agent needs to call it correctly or interpret the result is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline would be 3, but the description adds real boundary semantics the schema lacks: folder includes subfolders, matching is ASCII-case-insensitive, and "Projects" covers "Projects/Archive" but not "ProjectsOld/". Omitting the parameter to scan the whole vault is also spelled out.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ("discover all property keys in the vault") and immediately adds scope details (note counts and sample values) that separate it from a mere key enumeration. It also names the two nearest siblings (vault_list_property_values, vault_search_by_property) and states what each is for, so an agent can distinguish them without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

An explicit "When to use" section frames it as the discovery step before property search and as a companion to vault_list_tags. Two explicit "Prefer X when…" statements give the exact condition that switches the agent to a sibling tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_list_property_valuesList Property ValuesA
Read-onlyIdempotent

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

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

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. vault_search_by_property matches stored numbers numerically and text exactly; value "1" matches the number 1, the text "1", and a checked checkbox.

Parameters:

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

  • folder names a whole folder 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 true and false come back as "1" and "0", counted with the numbers 1 and 0.

  • null values are skipped.

Errors:

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

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

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

NameRequiredDescriptionDefault
keyYesProperty key name (e.g. "status", "type", "tags").
limitNoMax values to return (default 50).
folderNoRestrict to a folder (e.g. "Projects")

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only, idempotent, non-destructive behavior, but the description adds substantial context: scalar vs array counting, grouping of same displayed strings, checkbox encoding, null skipping, truncation signals, and error behavior. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads purpose and examples, then uses labeled sections for when-to-use, parameters, behavior, errors, and returns. The length is justified by the nuanced counting and truncation semantics and the absence of an output schema; no filler sentences.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers the return shape and sort order, edge cases, error behavior, and parameter interactions. With no output schema, the description still gives an agent everything needed to call and interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Even with 100% schema coverage, the description adds meaning beyond the brief schema descriptions for all three parameters. It explains key case-sensitivity and exact-match requirements, folder inclusion of subfolders with ASCII case-insensitive matching, and limit application after count sorting with no truncation indicator.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource: list distinct values for a property key with occurrence counts. It clearly distinguishes this from vault_list_property_keys (which lists keys) and vault_search_by_property (which searches notes by property).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use it: enumerate values before calling vault_search_by_property, and call vault_list_property_keys first to discover valid key names. It also explains how vault_search_by_property interprets values, reinforcing the alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_list_tagsList TagsA
Read-onlyIdempotent

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.

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

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

NameRequiredDescriptionDefault

No parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, but the description adds substantial semantics the agent could not infer: count means unique notes not occurrences, case-variant spellings are merged, hierarchical tags are not split, inline #tags are excluded, and the '#' prefix is stripped. It also discloses failure modes (empty array is not an error; SqliteError means the index failed) rather than the call.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core behavior and ordering, then an example, then when-to-use, then errors and returns. Section labels make it scannable, and each sentence supplies a distinct fact (case folding, hierarchy, unique-note counting) rather than restating the previous one.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, yet the description fully specifies the return payload via example plus a Returns line, and it covers the error surface. For a zero-parameter discovery tool this leaves nothing an agent needs unanswered.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are zero parameters, so the schema carries nothing to explain and the baseline is 4. The description nonetheless documents the output item shape ({ tag, count }, tag without '#'), which is genuinely useful since no output schema exists.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (list all tags in the vault) plus the ordering rule, and the closing 'When to use' section explicitly distinguishes it from vault_search_by_tag. An agent can separate this discovery tool from tag-query tools without reading any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit usage condition ('Discovering what tags exist before searching by tag. Good first step for vault orientation') and names the sibling to switch to with the exact condition that triggers the switch ('Prefer vault_search_by_tag once you know which tag'). Both when-to-use and when-to-use-something-else are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_list_tasksList TasksA
Read-onlyIdempotent

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 says where the task lives (path, line, heading; see Returns), so no follow-up reads are needed to locate it. 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: A file change takes a few seconds to appear here, so a task written moments ago may still show its old state.

Parameters:

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

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

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

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 …" — tag is "#" alone; pass the tag name

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

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

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.

NameRequiredDescriptionDefault
dueNoDue date (📅 / [due:: ]) bounds
tagNoTag on the task line, "#" optional; matches nested tags, any letter case
doneNoDone date (✅ / [completion:: ]) bounds
pathNoRestrict to one note (vault-relative path ending ".md", case-sensitive)
limitNoMax results (default 50); total always reports the full match count
startNoStart date (🛫 / [start:: ]) bounds
folderNoRestrict to a folder (e.g. "Code Projects/vault-cortex")
statusNoStatus filter, OR-combined (default "not_done" = todo + in_progress, excluding done and cancelled). "all" includes every status.not_done
createdNoCreated date (➕ / [created:: ]) bounds
headingNoExact heading text or array of headings, OR-combined, case-sensitive (e.g. "Active" or ["Active", "Up Next"])
sort_byNoSort key (default "due"). Date sorts cascade through related fields when the primary is absent (through the rest of due → scheduled → start → created, in that order; done does not cascade); each fallback uses its own natural direction. "position" sorts by file path then line number — the natural order for Kanban boards.due
priorityNoPriority levels, OR-combined; "none" selects tasks with no priority signifier
cancelledNoCancelled date (❌ / [cancelled:: ]) bounds
scheduledNoScheduled date (⏳ / [scheduled:: ]) bounds
sort_directionNoSort direction. Default per field: "asc" for due/scheduled/priority/position, "desc" for start/created/done/note_mtime. Within a date cascade, each fallback uses its own default; an explicit value overrides all fields uniformly.
top_level_onlyNoWhen true, only top-level tasks (depth 0) are returned — excludes indented sub-tasks and checklist items. Default false.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only/idempotent annotations, the description discloses indexing exclusions (fenced code blocks, comment blocks, NON_TASK statuses), freshness latency after writes, detailed error conditions, and return behavior. These are meaningful operational traits that annotations alone do not cover.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The definition is front-loaded with purpose, examples, usage guidance, and alternatives, which suits a 16-parameter tool. It is long but mostly structured and purposeful, though the examples and Returns section could be trimmed slightly without losing essential guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, 16 parameters, and lack of an output schema, the description is complete: it explains scope, indexing behavior, filtering semantics, alternatives, errors, and the full return shape including subtask_progress. An agent has enough information to call it correctly and interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents each parameter and enum. The description still adds useful semantics beyond the schema, such as date filters only matching tasks that actually have that date, folder subfolder/case behavior, status array expansion, and how top_level_only interacts with checklist items.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: list checkbox tasks across the vault with structured filters, and frames it as the Tasks-plugin data model over MCP. It clearly distinguishes this from sibling tools by naming vault_read_note and vault_search as alternatives for different needs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides explicit when-to-use guidance, multiple concrete examples for common queries, and clear alternatives: prefer vault_read_note for verbatim Markdown or post-write state, and vault_search for full-text content searches. This leaves little ambiguity about when to choose this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_memory_recallMemory RecallA
Read-onlyIdempotent

Recall memory entries about a topic — entry-granular hybrid (keyword + semantic) retrieval across ALL About Me/ files and ALL time. Returns the relevant dated entries, up to limit, sorted oldest-first, 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

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

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

NameRequiredDescriptionDefault
fileNoOptional: restrict to one memory file, name without .md (e.g. "Opinions"), in its exact letter case. Omit for cross-file recall — the default and usual choice.
limitNoCap on returned entries (default 50).
queryYesTopic to recall, in natural language.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (readOnly, idempotent, non-destructive), and the description adds substantive behavior beyond them: recall-over-precision bias with marginal entries expected, the relaxed any-term fallback for meta-framed queries, truncation semantics tied to limit, and pre-limit file filtering. Error behavior ('empty results, not an error'), unknown-file failure mode, and index-error disambiguation are all disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and scope, then structured into examples, usage, errors, and returns — every block is scannable. The Returns section is dense and somewhat lengthy, but with no output schema it is carrying necessary weight, so the length is largely earned.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Three parameters with 100% schema coverage, no output schema — and the description compensates by documenting the return shape (entries/total/truncated/search_mode/reranked) field by field, including how file and section feed into vault_get_memory. Nothing an agent needs to call or interpret results correctly is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline would be 3, but the description adds real meaning: content-word query phrasing ('testing philosophy') ranks best versus meta-framed queries, and crucially that the file filter is applied before limit so limit keeps the most relevant matches from that file. That interaction is not derivable from the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Recall memory entries about a topic') with the retrieval mechanism ('entry-granular hybrid keyword + semantic') and scope ('across ALL About Me/ files and ALL time'). An agent can distinguish this from vault_search and vault_get_memory without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The 'When to use' block names the exact question shapes this serves ('what does my memory say about X?') and explicitly routes to alternatives: vault_get_memory for known files/sections, vault_search for notes outside the memory layer. It also states the file-parameter default ('omit for cross-file recall — the default and usual choice').

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_move_noteMove NoteA
Destructive

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.

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

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

Parameters:

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

Errors:

  • "destination exists: …" — 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 path 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 (listed under When to use); pick a new_path outside it, or leave a protected note where it is.

  • "cannot read daily notes config from .obsidian/daily-notes.json" — the file exists but is unreadable; 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.

  • "concurrent write in progress" — a write is in flight on the note, the destination, or one of its backlink sources; 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.

  • "move incomplete: …" — an ordinary move failed partway (rare: a permission or disk error); no data is lost, and the error names what failed and the resulting state. 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 — "move aborted: could not rename to …" means nothing was written; retry the move, and if it fails again ask the vault's owner to fix the folder's permissions. "move incomplete: …" means 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 could not be saved with its links rewritten (the error says why and how to fix it); nothing was written; fix that note, then retry.

  • "move aborted: could not write to …" — new_path could not be created; nothing was written. The folder or disk can't be written; ask the vault's owner to fix it.

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

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

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

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

NameRequiredDescriptionDefault
new_pathYesDestination vault-relative path (e.g. "Projects/Spec.md"). Must end in .md and must not already exist; parent folders are created as needed. Use the exact letter case of existing folders; a different case can create a second folder.
old_pathYesCurrent vault-relative path of the note to move (e.g. "Inbox/Draft.md"). Must end in .md. Use the exact letter case.
prune_empty_foldersNoWhen true, also remove parent folders of old_path that the move leaves empty. Default false.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already declare destructiveHint=true and idempotentHint=false, but the description adds substantial context beyond them: it explains exactly which link forms are rewritten, how relative links are fixed, when links are intentionally left alone, and what never-overwrite behavior means. It also documents partial-failure states and recovery steps, so an agent understands the operational risk and expected behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded and clearly sectioned into purpose, examples, when-to-use, parameters, errors, syntax, and returns, so an agent can scan it efficiently. It is longer than typical, with an exhaustive error enumeration, but most of that content is actionable and earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex mutation that rewrites links and can partially fail, the description covers prerequisites, protected paths, parameter behavior, error recovery, and return values. Since no output schema exists, the explicit Returns section is especially important and is fully populated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents old_path, new_path, and prune_empty_folders thoroughly; a baseline of 3 would be defensible. The description adds useful meaning beyond the schema, especially for prune_empty_folders — explaining hidden-file retention, in-place rename behavior, and best-effort pruning — and supplies concrete path examples.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource — moving or renaming a note — and immediately states the distinctive behavior: rewriting every link across the vault. It distinguishes itself from siblings by naming vault_write_note + vault_delete_note, vault_patch_note, and vault_update_properties as alternatives for different tasks.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit when-to-use guidance, states which alternatives to prefer and why, and names the protected paths that cannot be moved. It also clarifies when to use other tools for body or property edits, leaving little ambiguity about tool selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_patch_notePatch NoteA
Destructive

Surgical edits to a markdown note — append, prepend, replace, or insert content by heading. Properties are kept as written, and CRLF line breaks are saved as LF.

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), below any empty lines that end it, so appending a row to a table that a blank line follows starts a new table; to extend a table, use vault_insert_at_anchor after its last row

  • 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); to empty it, use vault_delete_span on its lines

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

Heading-targeted ops keep the matched heading. Every operation writes content verbatim as whole lines with no separator: end content with a newline to leave a blank line after it; without one, the next line follows directly.

Limitation: If a no-heading prepend's content starts with a heading and the note has content above its first heading, that 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; if they share a level, rename one or use vault_replace_in_note to target by text content

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

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

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

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

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — 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: …", "EACCES: …" or another filesystem error code — the message says what to do

  • "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 any call that would have avoided it.

NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "TASKS.md", "Projects/plan.md"). Use the exact letter case.
contentYesMarkdown content to insert, or the section's new body for replace.
headingNoTarget heading text (case-sensitive exact match). Omit for a file-level append or prepend.
operationYesThe edit to apply.
heading_levelNoHeading level (1-6) for disambiguation when multiple headings share the same text
include_childrenNoWhen true, allows replace to overwrite a section that contains child headings.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark this as destructive and non-idempotent, and the description adds substantial context: section-boundary semantics, newline handling, the include_children guard, a no-heading prepend limitation, control-character errors, and concurrent-write retry guidance. It also explains return values and does not contradict the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with purpose and usage, then organized into operations, limitations, errors, syntax notes, and returns. It is long, but most of the length is justified by a destructive six-parameter tool with many named failure modes; the extensive error catalog is useful rather than pure padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, but the description explains the confirmation return format and special no-heading prepend return sentence. Combined with annotations and full schema coverage, it gives enough operational detail to call the tool correctly across normal and edge cases.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema coverage is 100%, the description adds operation-specific meaning well beyond the schema: append behavior around blank lines and tables, prepend callout behavior, replace requiring a heading, insert_before placement, heading_level disambiguation, and include_children confirmation. This is strong compensation even where the schema already documents fields.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence names the specific resource (markdown note) and verb set (append, prepend, replace, insert by heading), and immediately distinguishes surgical edits from whole-note writes. An agent can identify the tool without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit 'When to use' rule and then routes the agent away from this tool toward vault_write_note, vault_replace_in_note, vault_insert_at_anchor, vault_replace_span, vault_delete_span, vault_update_properties, and task tools under clear conditions. Alternative selection is fully specified.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_read_fileRead FileA
Read-onlyIdempotent

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

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

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.

  • 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; later pages are readable only as text, without raw.

Line paging: start_line and limit page any text result — text formats, canvas outlines and raw JSON, PDF-extracted text — as a 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; an unpaged read of a text format returns the file 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. 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: … is a markdown note" — the path ends in .md; read notes with vault_read_note

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

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

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

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

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

  • "line range is not available" (start_line or limit on an image, or on a PDF read with raw: true) / "raw source is not available for images" (raw on an image) — drop that input; 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; ask for a UTF-8 copy

  • "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; ask for a smaller export

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

  • "file changed while reading" — the file was written while it was read (for example by Sync); retry the read

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

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

Returns: 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.

NameRequiredDescriptionDefault
rawNoReturn a canvas's or PDF's alternative form. Changes nothing for text formats, which already return their source.
pathYesVault-relative path to the file, including its extension (e.g. "attachments/photo.png", "Boards/Roadmap.canvas"). Must NOT end in ".md". Use the exact letter case.
limitNoMaximum lines returned (default: all remaining).
start_lineNoFirst line to return, 1-based (default 1).

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish read-only/idempotent/non-destructive, and the description still adds substantial behavior beyond them: server-side downscaling thresholds (49152 bytes, 1568px), first-frame GIF reduction, the 5-page ceiling on raw PDF rendering, the 100 KiB per-window text cap, byte-exact vs. paged line endings, and the 50 MiB file cap. It is also honest about limits paging cannot overcome (one very long line overflowing the window).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and worked examples are front-loaded and every section maps to a real agent decision, but the exhaustive error catalog (roughly eighteen distinct messages) dominates the back half, and the decisive 'When to use' block sits after the long type/behavior tables. The content earns its place, but it could be tightened and reordered so the routing guidance appears earlier.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a four-parameter read tool with no output schema, the description covers return shapes per file type (image block plus metadata text, alternating page/image pairs, single text block), size and line caps, paging recovery, and per-error remediation. Nothing an agent needs to invoke this correctly or recover from failure is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema coverage is 100%, the description goes materially beyond the schema: it explains what raw:true actually changes for canvas (exact JSON vs. outline) and PDF (rendered pages vs. extracted text) and that it is a no-op for text formats, and it defines the paging contract for start_line/limit including the emitted window-metadata line and the continue-with hint.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence names a specific verb (read) and resource (non-markdown vault file) and explicitly positions itself as the read-side companion to vault_read_note, so the boundary against the closest sibling is unmistakable. The per-type table then enumerates exactly which extensions are in scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

A dedicated 'When to use' section states the trigger (a note references a file you need to see or read) and names the alternatives with their selection conditions: vault_get_outgoing_links to discover linked files, vault_list_files to browse folders, vault_read_note for .md, and the caveat that vault_search does not index images. It also gives a concrete pre-check recipe (start_line: 1 with limit: 1) for large files.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_read_noteRead NoteA
Read-onlyIdempotent

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. 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" — no note exists at this path; 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 …" — a full read, outline and heading still work; the error says how to repair it

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

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

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.

NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "About Me/Principles.md"). Use the exact letter case.
limitNoMaximum lines returned (default: all remaining).
headingNoReturn only this section. Case-sensitive exact match.
outlineNoIf true, returns { bytes, modified, leading_callout?, leading_content?, headings } as JSON instead of body content — a cheap structure fetch for large notes. headings: [{ level, text, bytes }]; leading_callout: { type, title, body } when the note has a top-of-file callout; leading_content: the rest of the body text above the first heading (callout lines excluded) when the note has any.
start_lineNoFirst line to return, 1-based (default 1). Pages the text output (full body or a heading section). Not valid for outline or properties_only (JSON modes).
heading_levelNoHeading level (1-6) for disambiguation when multiple headings share the same text; only applies with heading
properties_onlyNoIf true, returns parsed properties as JSON instead of full note content

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive, closed-world behavior, and the description does not contradict them. Beyond annotations, it discloses token-budget motivation for modes, mutual exclusivity of properties_only/outline/heading, section boundary semantics, paging metadata behavior, line-ending differences, and how YAML property errors affect available modes. These are meaningful behavioral details an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with purpose and mode overview, then examples, then usage and errors, so it is structurally sound. It is long, and the five examples plus full error catalog are close to excessive, but nearly all content earns its place for a seven-parameter tool with multiple modes.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description carries the burden of explaining return values, and it does so thoroughly: raw markdown, JSON properties, outline object, heading section markdown, paging metadata, and line-ending behavior. It also documents error conditions sufficiently for an agent to recover or choose another tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is already high, but the description adds substantial semantics: it states that properties_only, outline, and heading are mutually exclusive, that heading_level only disambiguates heading matches, and that start_line/limit only apply to text renditions. The examples concretely map parameters to expected call shapes.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific verb and resource: read a markdown note by vault-relative path. It immediately distinguishes the default full-content behavior from optional modes for properties, outline, and single sections, so an agent can tell exactly what the tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use guidance, including large-note workflow (outline then heading), paging oversized sections, and checking line counts. It names concrete alternatives for adjacent needs: vault_search for unknown paths, vault_list_tasks for board status, vault_get_memory for About Me files, vault_patch_note for editing, and backlink/outgoing-link tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_recent_notesRecent NotesA
Read-onlyIdempotent

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, including a text query limited to a date range (its created and modified filters). Prefer vault_search_by_folder for browsing a specific folder.

Parameters:

  • sort_by + limit interact: "modified" 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.

  • sort_by "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.

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

Returns: JSON array of note metadata (path, title, tags, related, folder (top-level folder), type, created, modified, bytes, leading_callout, additional_properties), newest first. The created field is null when the property is missing or not an ISO date; bytes is on-disk file size. A leading_callout field appears only when the note has one; additional_properties only when frontmatter has keys outside title, type, created, related, and tags.

NameRequiredDescriptionDefault
limitNoMax results (default 20, no upper cap)
sort_byNoTimestamp to sort by, newest first (default "modified")modified

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnly/idempotent/non-destructive), but the description adds real behavior: 'modified' uses filesystem mtime so sync touches register as recent, 'created' relies on frontmatter and undated notes sort last (so a small limit can silently exclude them), plus explicit error semantics (empty vault returns [], index errors are not call failures). This is context an agent cannot get from annotations or schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the scope statement, then examples, when-to-use, parameter notes, errors, and returns — each block earns its place. Even the long-looking Returns enumeration is justified because there is no output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description documents the returned fields (path, title, tags, folder, created, modified, bytes, leading_callout, additional_properties) and their conditional-presence/null rules, and covers error cases. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% (baseline 3), yet the description still adds meaning beyond the enum descriptions: it explains the interaction between sort_by and limit and the failure mode where undated notes fall outside a small limit under 'created'. That is genuine decision-relevant semantics, not restatement.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('List recently modified or created notes') and immediately scopes it ('a time-ordered window into the vault, not a date-range filter'), which pre-empts confusion with the date-filtering siblings. An agent can distinguish it from vault_search and vault_list_notes without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'When to use' section plus two named alternatives with the exact condition that selects each ('Prefer vault_search for content-based discovery, including a text query limited to a date range... Prefer vault_search_by_folder for browsing a specific folder'). No inference required.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_replace_in_noteReplace in NoteA
Destructive

Find and replace exact text in a markdown note's body. Properties are kept as written — edit them via vault_update_properties or vault_write_note's properties parameter. CRLF line breaks are saved as LF.

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

When to use: Targeted text changes within a single location — fixing typos, updating values, renaming terms, or removing a short line (new_text=""). 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.

  • new_text: non-empty new_text replaces the match exactly. A deletion (new_text="") that leaves text on its line is written exactly as asked. One that empties its line never doubles blank lines: the text above and below ends up separated by the wider of the two runs of empty lines that stood there, and by at least one; at the note's end the run above counts one fewer ("A\n\nB\n" without "B" → "A\n"). So include the line break in old_text to remove a table row or list item, or the empty line left behind splits the table or list. Empty lines elsewhere in the note stay as they are, and a line holding only spaces or tabs counts as text, not as an empty line.

  • replace_all_occurrences: 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: …", "EACCES: …" or another filesystem error code — the message says what to do

  • "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.

NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "Projects/plan.md"). Use the exact letter case.
new_textYesReplacement text. Empty string ("") deletes the matched text.
old_textYesExact text to find, no regex (case-sensitive).
replace_all_occurrencesNoReplace all occurrences (default: false — replaces first occurrence only)

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare destructive=true / idempotent=false, and the description goes well beyond them: CRLF normalized to LF, property block preserved as written, detailed blank-line compaction semantics for deletions, atomicity reasoning for cross-section moves, and an enumerated error taxonomy including concurrent-write and control-character cases. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, worked examples, and usage routing before reference material — good ordering with no filler sentences. The blank-line deletion rule is a single ~90-word run-on sentence that could be broken into bullets for parseability, which is the only structural weakness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, yet the description supplies the exact return string ('Replaced <N> occurrence(s) in <path>'), the full error catalogue with remediation hints, and Obsidian syntax pitfalls (#word, [[, %%). Nothing an agent needs in order to invoke or recover correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% (baseline 3), but the description adds real meaning for every parameter: uniqueness strategy for old_text (include surrounding context), exact-match deletion semantics for new_text including line-ending effects, and intent guidance ('deliberate bulk renames') for replace_all_occurrences.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource+scope: 'Find and replace exact text in a markdown note's body.' Immediately bounds the scope by stating properties are untouched and pointing to vault_update_properties / vault_write_note for that concern, which separates it cleanly from the other write-family siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'When to use' list (typos, value updates, term renames, short-line deletion) followed by named alternatives with the condition that selects each: vault_delete_span for large blocks, vault_replace_span for anchor-based block replacement, vault_patch_note for relocating content. It even explains why add-before-delete is safer (failure duplicates rather than loses content).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_replace_spanReplace SpanA
Destructive

Replace a contiguous block of whole lines in a note's body with new content, identified by short anchor substrings instead of the block's full text. Properties are kept as written, and CRLF line breaks are saved as LF.

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

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

Parameters:

  • end_anchor is searched at or after the start line and 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 (```): 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: line breaks at its start or end merge with the note's empty lines on that side, keeping the wider run (at the note's end, content's trailing line breaks count one fewer). So content can widen a gap but never narrow it: content with no line break at either end keeps the gaps as they were, and content ending in one ("| X |\n") leaves an empty line after it, which splits a table. Content made only of line breaks joins both sides into one gap. Empty lines inside content are written as given. Empty lines elsewhere in the note stay as they are, and a line holding only spaces or tabs counts as text, not as an empty line.

Errors:

  • "note not found" — verify path with vault_list_notes

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

  • "start anchor not found" / "end anchor not found" — no 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: …", "EACCES: …" or another filesystem error code — the message says what to do

  • "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 line(s) with line(s) in " — N counts the lines the span covered, M the lines in content as sent (its line breaks plus one).

NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "Tracker.md", "Notes/Plan.md"). Use the exact letter case.
contentYesReplacement content (one or more lines) — replaces every line of the matched span. Must be non-empty.
end_anchorNoShort substring that identifies the LAST line of the block (case-sensitive). Omit to replace just the single line containing start_anchor.
first_matchNoIf an anchor matches more than one line, use the first match instead of erroring (default: false — ambiguity is an error).
start_anchorYesShort, unique substring that identifies the first line of the block (case-sensitive). Pick a brief fragment — do not paste the whole block.

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and idempotentHint=false, but the description goes well past them: properties are preserved, CRLF is normalized to LF, ambiguity is an error unless first_match is set, and the code-block-fence case is explicitly a no-go. The error catalog (concurrent write, control characters, properties-block edge case) is genuinely additive. It stops short of 5 only because the escapes to vault_replace_in_note for the fence case are described but not summarized as a general failure-mode list.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with a one-sentence definition, then examples, then routing, then edge cases — a sound order. The content-line-break paragraph is dense and repetitive ("keeps the gaps as they were", "widen a gap but never narrow it", "joins both sides into one gap") and could be tightened without losing meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive mutation with no output schema, the description covers the safety profile, anchor resolution rules, content-merging semantics, Obsidian syntax gotchas, the exact return string with N/M line counts, and a remediation for every documented error. An agent could call this correctly and recover from failure without opening any other tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds semantics the schema cannot: end_anchor is searched at or after the start line and must be unique among those lines, and if both anchors land on the same line only that line is replaced. The long passage on how content's leading/trailing line breaks merge with surrounding empty lines (and how a trailing newline splits a table) is exactly the non-obvious behavior an agent would otherwise get wrong.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a precise verb+resource+mechanism: replace a contiguous block of whole lines, addressed by short anchor substrings rather than the block's literal text. The two worked examples make the granularity (one table row, one callout block) unmistakable and separate it from vault_replace_in_note at a glance.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Has an explicit 'When to use' clause (blocks already read, where reproducing old_text exactly is error-prone) plus two named alternatives with the condition that selects each: vault_replace_in_note for small in-place edits, vault_delete_span for removal without replacement. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_search_by_folderSearch by FolderA
Read-onlyIdempotent

Browse notes in a folder with full metadata.

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

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

Parameters:

  • folder names a whole folder, 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: Results can lag a file change by a few seconds: a note written moments ago may not appear yet, one renamed or deleted moments ago may still be listed, and a just-changed note may show its old metadata. Notes in hidden (dot-prefixed) folders never appear.

Errors:

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

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

Returns: JSON array of note metadata sorted by most recently modified, then by path: path, title, tags, related, folder (top-level 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).

NameRequiredDescriptionDefault
limitNoMax results (default 20)
folderYesFolder path (e.g. "Projects", "About Me")
recursiveNoInclude subfolders (default: true); false lists only the folder's top level

TDQS

A4.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so the safety profile is established structurally. The description nonetheless adds genuinely non-obvious behavior: index lag of a few seconds, hidden dot-folders never appearing, no truncation signal in the response, and empty-folder vs index-error differentiation. The only notable omission is pagination — there is a limit but no offset, which is left implicit rather than stated. Against an already-annotated read tool, this is solid added context rather than exceptional.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded one-line purpose, then call examples, then when-to-use, parameters, behavior, errors and returns in labeled blocks. Dense but every sentence carries an operational fact; no filler or restatement of the name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a three-parameter read tool with no output schema, the description closes all the gaps an agent needs: sorted return shape and field list, eventual-consistency caveat, hidden-folder exclusion, empty-result vs error disambiguation, and the truncation ambiguity. Nothing material is left to inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema cannot express: folder matching is whole-folder not text-prefix ("Projects" excludes "ProjectsOld/"), case-insensitive with trailing slash ignored, and limit is applied after sorting so it caps the newest N rather than paginating. Those are exactly the semantics that cause incorrect calls if misread.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Browse notes in a folder with full metadata") and immediately distinguishes itself from siblings that list paths only or search by text. An agent can tell from the first line that this is a folder-scoped read returning rich metadata, not a query-based search.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the alternatives and the conditions that select them: vault_list_notes for paths only, vault_search for text queries, backlink/outgoing-link tools for connectivity. It also names the positive case (folder exploration for vault orientation), so both when and when-not are covered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_search_by_propertySearch by PropertyA
Read-onlyIdempotent

Find notes where a frontmatter property matches a value.

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

When to use: Finding notes by metadata when you don't have a text query. Prefer vault_search when you also have a text query (it supports property filters too). Prefer vault_search_by_tag for tag-specific queries (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.

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

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.

NameRequiredDescriptionDefault
keyYesProperty key name (e.g. "status", "type", "tags").
limitNoMax results (default 20, no upper cap)
valueYesValue to match (e.g. "active", "4", "1e-7").
folderNoRestrict to a folder (e.g. "Projects")

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the read-only/idempotent/non-destructive profile, and the description adds genuinely non-obvious behavior: silent truncation ("nothing in the response signals truncation"), empty array instead of error for bad keys, index-failure error shapes, and mtime-based sort quirks with recently-synced notes. That is real disclosure beyond the structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, examples, then when-to-use, parameters, errors, returns — a clean scan order. It is dense and a few edge cases ("1e-999" matches 0, integers past 2^53 comparing equal) are more niche than an agent needs, but nothing is misordered or truly wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the return contract itself: field list, sort order, and the conditional appearance of leading_callout and additional_properties. Combined with the error section, an agent has everything needed to call and interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, but the description goes well beyond it: exact case-sensitive key/value matching, numeric coercion rules ("04" and "4.0" match number 4 but not text "4"), checkbox as "1"/"0" only, full-element array matching, folder subfolder inclusion and case-insensitivity, and that limit keeps most recently modified results.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Find notes where a frontmatter property matches a value") and immediately distinguishes itself from vault_search and vault_search_by_tag. The two inline examples make the call shape concrete at a glance.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use ("finding notes by metadata when you don't have a text query") plus named alternatives with the conditions that select them: vault_search when a text query exists, vault_search_by_tag for tag prefixes. It also routes discovery through vault_list_property_keys/values, so an agent never has to guess a key.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_search_by_tagSearch by TagA
Read-onlyIdempotent

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: Results can lag a file change by a few seconds: a note written moments ago may not appear yet, and one moved, deleted, or retagged moments ago may still be listed.

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 …" — tag is "#" alone; pass the tag name.

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

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).

NameRequiredDescriptionDefault
tagYesTag, "#" optional (e.g. "project", "session-log"). Nested tags use "/" (e.g. "project/vault-cortex").
exactNoMatch only the tag itself (default: false)
limitNoMax results (default 20, no upper cap)

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only/idempotent annotations, it discloses hierarchy matching semantics, case insensitivity, omission of inline tags, truncation ambiguity via limit, index lag after file changes, and error behavior. These are meaningful behavioral traits that annotations alone do not provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with purpose and matching rules, then uses clear sections for usage, parameters, behavior, errors, and returns. It is fairly long, but most content is valuable for correct invocation; the return-field enumeration is thorough rather than wasteful.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description fully describes the return structure, sorting, timestamps, and optional note fields. It also covers error cases, index lag, truncation caveats, and tag matching edge cases, so an agent has enough context to call and interpret results correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real semantics beyond the schema: exact controls subtree matching, limit applies after sorting and has no truncation indicator, and malformed tag values have specific error behavior. The added detail is useful, though some overlaps with schema descriptions prevent a top score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: find notes by frontmatter tag, ignoring case, and explicitly excludes inline #tags. It distinguishes tag-only lookup from text search and tag discovery, making the tool's role clear relative to siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says when to use tag-only lookups, when to use vault_list_tags first, and when to prefer vault_search for text relevance. It also explains nested-tag matching and the exact flag, leaving little ambiguity about tool choice or mode.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_update_memoryUpdate MemoryA
Idempotent

Append a dated entry to a section of a memory file in About Me/. 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).

Parameters:

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

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

Errors:

  • "entry must be a single line" — 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" / "memory file must be named without .md" — use a bare file name: no folder or slash, no leading dot, and no .md.

  • "section not created: … is nearly identical to existing section …" — the name differs from an existing heading only by an HTML-entity slip, a typo, or spacing; pass the exact existing heading (listed in the error) to append there, or choose a clearly different name for a genuinely new section. Names differing only in digits (e.g. "2025" vs "2026") count as different.

  • "cannot read memory file …" — the file can't be read: check file against vault_list_memory_files (a name longer than the filesystem allows fails here); otherwise its permissions need the vault's owner

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

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

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

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

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

NameRequiredDescriptionDefault
fileYesMemory file name without .md (e.g. "Principles"). Use the exact letter case; a different case can create a second file.
entryYesRaw entry text — a single line; the server prepends "- **YYYY-MM-DD**: " automatically. Do not include the date or bullet prefix.
optionsNoOptional date and position overrides
sectionYesH2 section heading (e.g. "Decision heuristics (newest first)"). Matched case-insensitively, with or without the "(newest first)" suffix.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes far beyond the annotations: explains idempotency semantics (duplicate = no-op, safe retry), the append-only default with newest-wins behavior, the living-file exception surfaced by vault_list_memory_files, auto-creation of missing files/sections, and the placeholder scope callout. It also enumerates concrete error cases and the exact return strings, which the annotations do not cover.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded, then examples, usage, parameters, syntax, errors, and returns in a scannable order; two worked examples clarify default vs. override behavior efficiently. The error catalog is long, but each entry maps to a distinct actionable failure mode, so little is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutating, nested-parameter tool with no output schema, the description covers side effects (file/section creation, placeholder callout), duplicate handling, return strings, and the full failure surface. An agent has everything needed to call it correctly and recover from errors.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real disambiguation: 'options.date only sets the bullet's date; placement follows options.position, never the date' resolves a non-obvious ambiguity, and the Obsidian Markdown escaping note (tags, wikilinks) tells the agent how to shape the entry value. Not a 5 because file/section naming rules largely restate schema descriptions and the error section rather than describing parameters directly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb + resource: 'Append a dated entry to a section of a memory file in About Me/'. This is clearly distinguishable from siblings like vault_write_note, vault_get_memory, vault_delete_memory, and vault_replace_in_note without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Has an explicit 'When to use' section (recording a preference, principle, opinion, or fact), a stated prerequisite ('Call vault_list_memory_files first and reuse existing file and section names'), and a named alternative with its condition ('Prefer vault_write_note for creating non-memory notes'). It also documents the append-vs-prune policy and the `entry-policy: living` exception.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_update_propertiesUpdate PropertiesA
DestructiveIdempotent

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

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

  • "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.

NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension. Use the exact letter case.
replaceNoReplace instead of merging (default: false).
propertiesYesProperties to merge, or with replace the complete new set.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations (destructiveHint=true, idempotentHint=true) by spelling out merge vs replace semantics, that null deletes in merge but writes an empty property in replace, that {} removes a property under replace, that the body is preserved, and that a merge refuses invalid YAML/lists/tags. It also catalogues concurrency and path-guard errors with recovery steps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well-segmented with labeled Example / When to use / Errors / Returns blocks, and the core semantics are front-loaded. The eight-bullet error catalogue is a little heavy for a definition, but each entry carries actionable recovery guidance rather than filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive mutation with a nested object parameter and no output schema, the description supplies examples, error conditions, return strings, merge/replace edge cases, Obsidian syntax cautions, and sibling routing. An agent could invoke it correctly on the first try.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema baseline is 3, but the description adds real meaning the schema lacks: how null and {} are interpreted inside `properties`, that arrays are replaced rather than appended, and how `replace: true` changes the interpretation of both parameters. It stops short of documenting the `propertyNames`/nesting constraints, so not a full 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource: "Update a note's frontmatter properties." It immediately scopes the operation to frontmatter (not the body) and later names the sibling it is not, vault_write_note, so an agent can distinguish it without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

A dedicated "When to use" section states the scenario (changing tags/status/type without rewriting the body), names alternatives and when to prefer them (vault_write_note for creating or replacing the body), and prescribes a read-first workflow with vault_read_note({ properties_only: true }). Exclusions and prerequisites are explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_update_taskUpdate TaskA
Destructive

Update a task's status, priority, description, dates, recurrence, completion action, dependencies, block_id, checklist items, heading, or position within a lane, in any combination; every field passed is written in a single edit.

Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", status: "done" }) — complete a task; on a Kanban board, auto-moves to the done lane Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", recurrence: "every week" }) — make a task recurring Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", heading: "Done" }) — move a task to a different heading (the checkbox is unchanged) Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", description: "Updated task name", due: "2026-10-01", scheduled: null }) — change the description, set one date, and clear another Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", status: "in_progress", add_subtasks: ["Design", "Implement", "Test"] }) — start working and add checklist stages Example: vault_update_task({ path: "TASKS.md", line: 42, assign_block_id: "my-task" }) — add a block_id to a task that lacks one 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. Use vault_list_tasks first to get identification fields (path + block_id or line). For creating a new task, use vault_create_task instead.

Parameters:

  • Exactly one of block_id or line is required to identify the task. A Kanban sub-task this server created has no block_id, so re-read its line with vault_list_tasks just before an update by line: each save by the Kanban plugin rewrites the board. The listing lags each save by a few seconds, so wait that long after a write before re-reading.

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

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

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

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

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

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

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

  • "absolute path blocked" / "path traversal blocked" / "hidden path blocked" — 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" / "blockId ... is inside a fenced code block or comment" — no task line outside a fenced code block or %% %% comment ends with that ^id; check the id with vault_list_tasks, or target by line

  • "blockId ... matches N task lines" — 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), then assign the card the id its existing links use, or a new one

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

  • "checkbox "[c]" is a NON_TASK status" / "no checkbox symbol for status ..." — in the Tasks plugin's status settings, the targeted task's checkbox character (c) is typed NON_TASK so it is not a task, or the status the message names has no symbol and its default character has another type; the user must change that character's type or add a symbol for that status 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" / "cannot reposition a sub-task" — a heading or position on a task nested under another task (depth > 0 in vault_list_tasks); sub-tasks move with their parent, so move or reposition the parent instead

  • "cannot reorder a task that sits above the first heading" — position without a heading on a task before the first section heading; pass heading to move it into a section

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

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

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

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

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

  • "invalid date: …" — a date param isn't in YYYY-MM-DD form or names a day that doesn't exist; pass a real YYYY-MM-DD date

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

  • "taskId ... contains invalid characters" / "dependsOn entry ... contains invalid characters" — task_id and every depends_on entry must match [a-zA-Z0-9_-]+

  • "unrecognized recurrence rule ..." — the rule text is not Tasks-plugin natural language; 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

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

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

  • "the note would open with a properties block …" — 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: 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. The write still succeeds either way; reword the text to clear any advisories the result carries. 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, and block_id and heading reflect the task after the update; block_id is omitted when the task has none, heading when the task sits above the first heading.

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

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

  • changes lists every field applied as "field: before → after", with "(none)" for an absent value. An add_subtasks change 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.

NameRequiredDescriptionDefault
dueNoDue date (YYYY-MM-DD) to set, or null to clear.
lineNo1-based line number from vault_list_tasks. Fragile if the file changed since the query.
pathYesVault-relative path to the note containing the task (must end in ".md"). Use the exact letter case.
startNoStart date (YYYY-MM-DD) to set, or null to clear.
formatNoField format for metadata this call writes; fields it leaves alone keep theirs, so a line can mix formats. Default: auto-detected from .obsidian/ config, falling back to emoji.
statusNoTarget status. "done" adds the ✅ date and "cancelled" the ❌ date, when the Tasks plugin's settings stamp them; moving a task out of either status removes its date.
createdNoCreated date (YYYY-MM-DD) to set or clear, for corrections. vault_create_task stamps it on every new task.
headingNoTarget heading to move the task to. On Kanban boards this is a lane move; works on any note with headings. Not valid on sub-tasks.
task_idNoTasks plugin 🆔 identifier to set, or null to clear — letters, digits, hyphens, and underscores; separate from block_id.
block_idNoStable task identifier — the ^block-id at the end of the task line, without the ^. Preferred over line.
positionNoWhere within the target heading the task lands. "top" or "bottom" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike "bottom", which appends after any non-task text at the end of the section). Defaults to "top" on heading and done-lane moves.
priorityNoPriority signifier to set, or null to remove it.
scheduledNoScheduled date (YYYY-MM-DD) to set, or null to clear.
depends_onNoTasks plugin ⛔ dependency IDs to set (non-empty), or null to clear.
recurrenceNoTasks plugin 🔁 rule in natural language (e.g. "every week", "every month on the 15th", "every 2 weeks when done") to set, or null to remove it.
descriptionNoNew task description text. Replaces the existing description; metadata fields and block_id are preserved.
add_subtasksNoChecklist items to append, one indented todo line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended, and not reported, when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id.
on_completionNoTasks plugin 🏁 onCompletion action to set, or null to remove it. "keep" leaves the task in place on completion, as having no 🏁 field does.
assign_block_idNoAdd or replace the ^block-id on the task line, or null to remove it. Letters, digits, and hyphens only; must be unique within the note.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag destructive=true, idempotentHint=false and openWorld=false, but the description goes far beyond: it discloses Kanban auto-move semantics, that completing by line is not idempotent for recurring tasks (spawn occupies the old line), nested-line behavior, concurrency errors, and the delete-on-completion path. This is exactly the mutation risk context an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well front-loaded — purpose sentence, then examples, then usage, parameters, errors, syntax, returns. The very long error catalog and seven examples push it past what's needed for tool selection, though each item is actionable once invoked. Not wasteful, but heavier than the selection decision requires.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 19-parameter destructive mutation tool with no output schema, the description compensates fully: it documents the return object field-by-field (path, line, block_id, subtasks, next_occurrence, changes, advisories, on_completion_applied), the side effects, and the failure modes. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, yet the description still adds cross-parameter rules the schema cannot express: exactly one of block_id/line, 'at least one change' required, explicit null to clear vs. omission to leave untouched, on_completion overriding a stored delete, and position's interaction with heading/done-lane moves. This is genuine value beyond the structured fields.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb plus an enumerated set of updatable fields, and explicitly contrasts itself with vault_create_task for the create case. An agent can distinguish it from every sibling (vault_create_task, vault_list_tasks, vault_replace_span) without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States 'When to use: Any change to an existing task', names the prerequisite lookup (vault_list_tasks for path + block_id/line), and routes creation to vault_create_task. Seven worked examples cover the ambiguous cases (recurrence, heading move, clearing a date, assign_block_id).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_write_noteWrite NoteA
Destructive

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

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

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

  • "the note would open with a properties block 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 ".

NameRequiredDescriptionDefault
bodyYesMarkdown body content — do not include frontmatter fences (---); use the properties parameter instead.
pathYesVault-relative path including the ".md" extension (e.g. "Projects/notes.md"). Parent folders are created as needed. Use the exact letter case; a different case can create a duplicate note or folder.
overwriteNoAllow overwriting an existing note (default: false).
propertiesNoProperties to merge.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations flag destructiveHint=true, and the description substantiates exactly what is destroyed: body replaces the entire note content and existing content is lost unless re-included. It also discloses the non-obvious property merge rules (null removes keys, unmentioned keys preserved, new note drops null keys), concurrency behavior, and a detailed error catalogue — context the annotations cannot convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Core mutation semantics and the alternatives are correctly front-loaded before the error list, and both examples are compact and illustrative. The error catalogue is long and a few entries carry little weight ("ENAMETOOLONG/EACCES — the message says what to do" essentially says nothing), which costs a point on density.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive 4-param write tool with no output schema, the description covers the return string ("Wrote <path>"), every foreseeable failure with a recovery action, and the merge model for nested properties. Nothing an agent needs to call this correctly and recover from errors is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description meaningfully exceeds the schema for `properties`, which is documented only as "Properties to merge" — the null-removal, overwrite, and preservation semantics are supplied here. It also adds Obsidian-specific encoding rules (escape \#, quote wikilinks, keep property types consistent) that the schema omits, though `path` guidance largely restates the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource ("Create a markdown note") and immediately bounds the scope with overwrite semantics. It explicitly separates itself from siblings vault_patch_note, vault_replace_in_note, vault_update_properties, and vault_update_memory, so an agent can route correctly without opening other schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides a dedicated "When to use" line plus explicit alternatives with the selecting condition ("Prefer vault_update_properties for property-only edits", "Prefer vault_update_memory for appending dated entries"). It also states the exact condition for setting overwrite: true, leaving nothing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 10 tool updatesv0.55.1
    • Changedvault_delete_memory2 fields changed
      • changedInput schema / properties / date / description
        Previous value: -"ISO YYYY-MM-DD date of the entry (e.g. \"2026-05-01\"). Must match the date shown by vault_get_memory."New value: +"ISO YYYY-MM-DD date of the entry (e.g. \"2026-05-01\")."
      • changedInput schema / properties / entry / description
        Previous value: -"Exact entry text as shown by vault_get_memory — without the \"- **YYYY-MM-DD**: \" prefix or bullet."New value: +"Exact entry text, without the \"- **YYYY-MM-DD**: \" prefix or bullet."
    • Changedvault_get_daily_note1 field changed
      • changedInput schema / properties / date / description
        Previous value: -"YYYY-MM-DD (e.g. \"2026-05-13\", \"2025-12-31\"). Defaults to today in the server's timezone. Invalid formats like \"May 13\" return an error."New value: +"YYYY-MM-DD (e.g. \"2026-05-13\", \"2025-12-31\"). Defaults to today in the server's timezone."
    • Changedvault_get_memory2 fields changed
      • changedInput schema / properties / on_or_after / description
        Previous value: -"Inclusive date filter (YYYY-MM-DD). When provided with file, returns structured JSON entries dated on or after this date instead of raw markdown. Requires a file; add section to scope to one H2 section."New value: +"Inclusive date filter (YYYY-MM-DD). Requires a file."
      • changedInput schema / properties / section / description
        Previous value: -"H2 section heading (e.g. \"Decision heuristics (newest first)\"). Matched case-insensitively, with or without the \"(newest first)\" suffix. Omit to read the whole file (with on_or_after, every H2 section's entries). Call vault_list_memory_files first to discover valid names."New value: +"H2 section heading (e.g. \"Decision heuristics (newest first)\"). Matched case-insensitively, with or without the \"(newest first)\" suffix. Omit to read the whole file."
    • Changedvault_list_property_values1 field changed
      • changedInput schema / properties / key / description
        Previous value: -"Property key name — use vault_list_property_keys to discover valid keys (e.g. \"status\", \"type\", \"tags\")."New value: +"Property key name (e.g. \"status\", \"type\", \"tags\")."
    • Changedvault_memory_recall1 field changed
      • changedInput schema / properties / query / description
        Previous value: -"Topic to recall — natural language works best (semantic matching bridges phrasing drift across months); content words about the topic rank better than meta framing (\"testing philosophy\" over \"opinions on testing\")"New value: +"Topic to recall, in natural language."
    • Changedvault_patch_note1 field changed
      • changedInput schema / properties / operation / description
        Previous value: -"append | prepend | replace | insert_before."New value: +"The edit to apply."
    • Changedvault_replace_in_note1 field changed
      • changedInput schema / properties / old_text / description
        Previous value: -"Exact text to find (case-sensitive)."New value: +"Exact text to find, no regex (case-sensitive)."
    • Changedvault_search1 field changed
      • changedInput schema / properties / include_leading_callout / description
        Previous value: -"If true, include each result's leading callout. Off by default to keep results lean."New value: +"If true, include each result's leading callout."
    • Changedvault_update_memory1 field changed
      • changedInput schema / properties / entry / description
        Previous value: -"Raw entry text — a single line (newlines are rejected); the server prepends \"- **YYYY-MM-DD**: \" automatically. Do not include the date or bullet prefix."New value: +"Raw entry text — a single line; the server prepends \"- **YYYY-MM-DD**: \" automatically. Do not include the date or bullet prefix."
    • Changedvault_update_task1 field changed
      • changedInput schema / properties / status / description
        Previous value: -"Target status. \"done\" adds the ✅ date and \"cancelled\" the ❌ date, when the Tasks plugin's settings stamp them; leaving either status removes its date."New value: +"Target status. \"done\" adds the ✅ date and \"cancelled\" the ❌ date, when the Tasks plugin's settings stamp them; moving a task out of either status removes its date."
  2. 31 tool updatesv0.54.16
    • Changedvault_create_task1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_delete_memory1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_delete_note1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_delete_span3 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / end_anchor / description
        Previous value: -"Short substring that identifies the LAST line of the block. The entire line is selected. Omit to delete just the single line containing start_anchor."New value: +"Short substring that identifies the LAST line of the block (case-sensitive). Omit to delete just the single line containing start_anchor."
      • changedInput schema / properties / start_anchor / description
        Previous value: -"Short, unique substring that identifies the first line of the block (case-sensitive). The entire line is selected, not just the substring. Pick a brief fragment — do not paste the whole block."New value: +"Short, unique substring that identifies the first line of the block (case-sensitive). Pick a brief fragment — do not paste the whole block."
    • Changedvault_find_orphans1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_get_backlinks1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_get_daily_note1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_get_memory1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_get_outgoing_links1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_insert_at_anchor1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_list_files1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_list_notes1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_list_property_keys1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_list_property_values1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_list_tasks7 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • addedInput schema / properties / cancelled / additionalProperties
        Added value: +false
      • addedInput schema / properties / created / additionalProperties
        Added value: +false
      • addedInput schema / properties / done / additionalProperties
        Added value: +false
      • addedInput schema / properties / due / additionalProperties
        Added value: +false
      • addedInput schema / properties / scheduled / additionalProperties
        Added value: +false
      • addedInput schema / properties / start / additionalProperties
        Added value: +false
    • Changedvault_memory_recall1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_move_note1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_patch_note2 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / content / description
        Previous value: -"Markdown content to insert."New value: +"Markdown content to insert, or the section's new body for replace."
    • Changedvault_read_file3 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the file, including its extension (e.g. \"attachments/photo.png\", \"Boards/Roadmap.canvas\"). Must NOT end in \".md\" — notes are read with vault_read_note. Use the exact letter case."New value: +"Vault-relative path to the file, including its extension (e.g. \"attachments/photo.png\", \"Boards/Roadmap.canvas\"). Must NOT end in \".md\". Use the exact letter case."
      • changedInput schema / properties / raw / description
        Previous value: -"Return the file's alternative form: JSON source for .canvas, page images for .pdf. Changes nothing for text formats, which already return their source. Rejected for images."New value: +"Return a canvas's or PDF's alternative form. Changes nothing for text formats, which already return their source."
    • Changedvault_read_note1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_recent_notes1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_replace_in_note2 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / old_text / description
        Previous value: -"Exact text to find (case-sensitive). Matches in the body only — text inside frontmatter properties is not searched."New value: +"Exact text to find (case-sensitive)."
    • Changedvault_replace_span3 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / end_anchor / description
        Previous value: -"Short substring that identifies the LAST line of the block. The entire line is selected. Omit to replace just the single line containing start_anchor."New value: +"Short substring that identifies the LAST line of the block (case-sensitive). Omit to replace just the single line containing start_anchor."
      • changedInput schema / properties / start_anchor / description
        Previous value: -"Short, unique substring that identifies the first line of the block (case-sensitive). The entire line is selected, not just the substring. Pick a brief fragment — do not paste the whole block."New value: +"Short, unique substring that identifies the first line of the block (case-sensitive). Pick a brief fragment — do not paste the whole block."
    • Changedvault_search8 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • addedInput schema / properties / filters / additionalProperties
        Added value: +false
      • addedInput schema / properties / filters / properties / created / additionalProperties
        Added value: +false
      • addedInput schema / properties / filters / properties / modified / additionalProperties
        Added value: +false
      • changedInput schema / properties / filters / properties / modified / description
        Previous value: -"Modified date bounds (YYYY-MM-DD) on filesystem modified time, server-local day boundaries"New value: +"Modified date bounds (YYYY-MM-DD) on filesystem modified time"
      • changedInput schema / properties / filters / properties / related / description
        Previous value: -"Require all listed related links"New value: +"Require all listed related links, each written exactly as in the note's related property, brackets and alias included (e.g. \"[[Projects/alpha|Alpha]]\")"
      • changedInput schema / properties / filters / properties / type / description
        Previous value: -"Match the frontmatter type field (exact match, e.g. \"person\", \"meeting\")"New value: +"Match the frontmatter type field (exact, case-sensitive match, e.g. \"person\", \"meeting\")"
      • changedInput schema / properties / snippet_tokens / description
        Previous value: -"Snippet length in words (default 30)"New value: +"Snippet length in words, not model tokens (default 30)"
    • Changedvault_search_by_folder1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_search_by_property1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_search_by_tag1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_update_memory2 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • addedInput schema / properties / options / additionalProperties
        Added value: +false
    • Changedvault_update_properties1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
    • Changedvault_update_task3 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedInput schema / properties / on_completion / description
        Previous value: -"Tasks plugin 🏁 onCompletion action to set, or null to remove it. \"delete\" removes the task line on completion; \"keep\" leaves it in place (which is also the behavior when no 🏁 field exists on the task). Omitting this parameter leaves the field unchanged."New value: +"Tasks plugin 🏁 onCompletion action to set, or null to remove it. \"keep\" leaves the task in place on completion, as having no 🏁 field does."
      • changedInput schema / properties / recurrence / description
        Previous value: -"Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every month on the 15th\", \"every 2 weeks when done\") to set, or null to remove it. Completing the task spawns its next occurrence."New value: +"Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every month on the 15th\", \"every 2 weeks when done\") to set, or null to remove it."
    • Changedvault_write_note1 field changed
      • addedInput schema / additionalProperties
        Added value: +false
  3. 11 tool updatesv0.54.13
    • Changedvault_create_task9 fields changed
      • changedInput schema / properties / block_id / description
        Previous value: -"The ^block-id for stable identification — letters, digits, and hyphens only ([a-zA-Z0-9-]+). Must be unique within the note."New value: +"The ^block-id (without the ^) for stable identification — letters, digits, and hyphens only ([a-zA-Z0-9-]+), unique within the note."
      • changedInput schema / properties / depends_on / description
        Previous value: -"Tasks plugin ⛔ dependency IDs (🆔 values of other tasks). Non-empty; omit when there are no dependencies."New value: +"Tasks plugin ⛔ dependency IDs (🆔 values of other tasks), not checked against existing tasks. Non-empty; omit when there are no dependencies."
      • changedInput schema / properties / heading / description
        Previous value: -"Target heading. On a regular note, omit to append at end of body."New value: +"Target heading's text, without the # marks, matched exactly. On a regular note, omit to append at end of body."
      • changedInput schema / properties / on_completion / description
        Previous value: -"Tasks plugin 🏁 onCompletion action. \"delete\" removes the task line on completion; \"keep\" leaves it in place."New value: +"Tasks plugin 🏁 onCompletion action. \"delete\" removes the task line and its children on completion; \"keep\" leaves it in place, as omitting the field does."
      • changedInput schema / properties / position / description
        Previous value: -"Where within the heading section the task is placed. \"top\" or \"bottom\" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike \"bottom\", which appends after any non-task text at the end of the section). Defaults to bottom."New value: +"Where within the heading section the task is placed: \"top\" first in the section, \"bottom\" at the end of the section, or a 1-based integer for an exact slot among the section's top-level tasks (a Kanban lane's cards; nested tasks are not counted). A position past the count lands directly below the last top-level task (unlike \"bottom\", which appends after any non-task text at the end of the section). Defaults to \"bottom\" (\"top\" on Kanban boards set to new-card-insertion-method: \"prepend\")."
      • changedInput schema / properties / priority / description
        Previous value: -"Priority signifier (🔺⏫🔼🔽⏬). Omit for normal priority — no signifier is written."New value: +"Priority signifier: highest 🔺, high ⏫, medium 🔼, low 🔽, lowest ⏬. Omit for normal priority — no signifier is written."
      • changedInput schema / properties / subtasks / description
        Previous value: -"Checklist item descriptions — created as indented todo lines under the card (no metadata). For full sub-tasks with dates, priority, and block_id, make a separate call with parent_block_id."New value: +"Checklist sub-items: text-only todo lines indented under the card, with no metadata (not even ➕). For a sub-task with dates or priority, make a separate call with parent_block_id."
      • changedInput schema / properties / task_id / description
        Previous value: -"Tasks plugin 🆔 identifier other tasks can name in depends_on."New value: +"Tasks plugin 🆔 identifier other tasks can name in depends_on — letters, digits, hyphens, and underscores; separate from block_id."
      • changedInput schema / required
        Previous value: -[
        -  "path",
        -  "description",
        -  "block_id"
        -]New value: +[
        +  "path",
        +  "description"
        +]
    • Changedvault_find_orphans1 field changed
      • changedInput schema / properties / exclude_folders / description
        Previous value: -"Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")"New value: +"Folder paths to exclude (e.g. Projects; default: the daily notes folder, \"Templates\", \"About Me\")"
    • Changedvault_list_tasks1 field changed
      • changedInput schema / properties / tag / description
        Previous value: -"Inline task tag, bare name without \"#\"; parent tags match children"New value: +"Tag on the task line, \"#\" optional; matches nested tags, any letter case"
    • Changedvault_patch_note2 fields changed
      • changedInput schema / properties / content / description
        Previous value: -"Markdown content to insert. Must not begin with the target heading text (it would duplicate the heading, which is kept automatically)."New value: +"Markdown content to insert."
      • changedInput schema / properties / include_children / description
        Previous value: -"When true, allows replace to overwrite a section that contains child headings. Without this, replace errors if children exist — preventing silent data loss."New value: +"When true, allows replace to overwrite a section that contains child headings."
    • Changedvault_recent_notes1 field changed
      • changedInput schema / properties / sort_by / description
        Previous value: -"Sort order (default \"modified\")"New value: +"Timestamp to sort by, newest first (default \"modified\")"
    • Changedvault_search1 field changed
      • changedInput schema / properties / filters / properties / tags / description
        Previous value: -"Require all listed tags (AND — every tag must be present)"New value: +"Tags, \"#\" optional (e.g. [\"reference\"])"
    • Changedvault_search_by_property3 fields changed
      • changedInput schema / properties / key / description
        Previous value: -"Property key name (e.g. \"status\", \"type\", \"tags\"). Use vault_list_property_keys to discover valid keys."New value: +"Property key name (e.g. \"status\", \"type\", \"tags\")."
      • changedInput schema / properties / limit / description
        Previous value: -"Max results (default 20)"New value: +"Max results (default 20, no upper cap)"
      • changedInput schema / properties / value / description
        Previous value: -"Value to match (e.g. \"active\", \"4\", \"1e-7\"). Use vault_list_property_values to discover valid values for a key."New value: +"Value to match (e.g. \"active\", \"4\", \"1e-7\")."
    • Changedvault_search_by_tag3 fields changed
      • changedInput schema / properties / exact / description
        Previous value: -"Exact match only (default: false, prefix match)"New value: +"Match only the tag itself (default: false)"
      • addedInput schema / properties / limit
        Added value: +{
        +  "default": 20,
        +  "description": "Max results (default 20, no upper cap)",
        +  "maximum": 9007199254740991,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • changedInput schema / properties / tag / description
        Previous value: -"Tag name without \"#\" prefix (e.g. \"project\", \"session-log\"). Hierarchical tags use \"/\" separators (e.g. \"project/vault-cortex\")."New value: +"Tag, \"#\" optional (e.g. \"project\", \"session-log\"). Nested tags use \"/\" (e.g. \"project/vault-cortex\")."
    • Changedvault_update_properties2 fields changed
      • changedInput schema / properties / properties / description
        Previous value: -"Properties to merge; a null value deletes that key."New value: +"Properties to merge, or with replace the complete new set."
      • addedInput schema / properties / replace
        Added value: +{
        +  "default": false,
        +  "description": "Replace instead of merging (default: false).",
        +  "type": "boolean"
        +}
    • Changedvault_update_task10 fields changed
      • changedInput schema / properties / add_subtasks / description
        Previous value: -"Checklist items to append, one indented todo line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id."New value: +"Checklist items to append, one indented todo line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended, and not reported, when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id."
      • addedInput schema / properties / assign_block_id / anyOf
        Added value: +[
        +  {
        +    "minLength": 1,
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / assign_block_id / description
        Previous value: -"Add or replace the ^block-id on the task line. Letters, digits, and hyphens only; must be unique within the note."New value: +"Add or replace the ^block-id on the task line, or null to remove it. Letters, digits, and hyphens only; must be unique within the note."
      • removedInput schema / properties / assign_block_id / minLength
        Removed value: -1
      • removedInput schema / properties / assign_block_id / type
        Removed value: -"string"
      • changedInput schema / properties / created / description
        Previous value: -"Created date (YYYY-MM-DD) to set or clear. Typically auto-stamped; use for corrections."New value: +"Created date (YYYY-MM-DD) to set or clear, for corrections. vault_create_task stamps it on every new task."
      • changedInput schema / properties / format / description
        Previous value: -"Field format for new metadata. Default: auto-detected from .obsidian/ config, falling back to emoji."New value: +"Field format for metadata this call writes; fields it leaves alone keep theirs, so a line can mix formats. Default: auto-detected from .obsidian/ config, falling back to emoji."
      • changedInput schema / properties / position / description
        Previous value: -"Where within the target heading the task lands. \"top\" or \"bottom\" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike \"bottom\", which appends after any non-task text at the end of the section). Defaults to \"top\" on heading moves."New value: +"Where within the target heading the task lands. \"top\" or \"bottom\" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike \"bottom\", which appends after any non-task text at the end of the section). Defaults to \"top\" on heading and done-lane moves."
      • changedInput schema / properties / status / description
        Previous value: -"Target status. \"done\" appends the ✅ date; \"cancelled\" appends the ❌ date."New value: +"Target status. \"done\" adds the ✅ date and \"cancelled\" the ❌ date, when the Tasks plugin's settings stamp them; leaving either status removes its date."
      • changedInput schema / properties / task_id / description
        Previous value: -"Tasks plugin 🆔 identifier to set, or null to clear."New value: +"Tasks plugin 🆔 identifier to set, or null to clear — letters, digits, hyphens, and underscores; separate from block_id."
    • Changedvault_write_note2 fields changed
      • changedInput schema / properties / overwrite / description
        Previous value: -"Allow overwriting an existing note (default: false — errors if file exists)."New value: +"Allow overwriting an existing note (default: false)."
      • changedInput schema / properties / properties / description
        Previous value: -"Optional properties to merge; a null value deletes that key."New value: +"Properties to merge."
  4. 18 tool updatesv0.54.9
    • Changedvault_create_task5 fields changed
      • changedInput schema / properties / heading / description
        Previous value: -"Target heading. Required on Kanban boards; optional on regular notes (omit to append at end of body)."New value: +"Target heading. On a regular note, omit to append at end of body."
      • changedInput schema / properties / parent_block_id / description
        Previous value: -"^block-id (without the ^) of an existing task to nest under as a sub-task. Mutually exclusive with parent_line and heading."New value: +"^block-id (without the ^) of an existing task to nest under as a sub-task."
      • changedInput schema / properties / parent_line / description
        Previous value: -"1-based line number of an existing task to nest under as a sub-task. Mutually exclusive with parent_block_id and heading. Fragile if the file changed since the line was read."New value: +"1-based line number of an existing task to nest under as a sub-task. Fragile if the file changed since the line was read."
      • changedInput schema / properties / position / description
        Previous value: -"Where within the heading section the task is placed. \"top\" or \"bottom\" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike \"bottom\", which appends after any trailing section content). Defaults to bottom. Kanban boards with new-card-insertion-method set to prepend default to top instead. Ignored when no heading or when placing under a parent."New value: +"Where within the heading section the task is placed. \"top\" or \"bottom\" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike \"bottom\", which appends after any non-task text at the end of the section). Defaults to bottom."
      • changedInput schema / properties / recurrence / description
        Previous value: -"Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every 2 weeks when done\"). Completing the task spawns its next occurrence."New value: +"Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every month on the 15th\", \"every 2 weeks when done\"). Completing the task spawns its next occurrence."
    • Changedvault_delete_memory1 field changed
      • changedInput schema / properties / entry / description
        Previous value: -"Exact entry text as shown by vault_get_memory — without the \"- **YYYY-MM-DD**: \" prefix or bullet. Both date and entry must match for deletion."New value: +"Exact entry text as shown by vault_get_memory — without the \"- **YYYY-MM-DD**: \" prefix or bullet."
    • Changedvault_delete_span1 field changed
      • changedInput schema / properties / end_anchor / description
        Previous value: -"Short, unique substring that identifies the LAST line of the block, searched at or after the start_anchor line. The entire line is selected. Omit to delete just the single line containing start_anchor."New value: +"Short substring that identifies the LAST line of the block. The entire line is selected. Omit to delete just the single line containing start_anchor."
    • Changedvault_find_orphans1 field changed
      • changedInput schema / properties / exclude_folders / description
        Previous value: -"Folders to exclude — replaces the defaults ([\"Daily Notes\",\"Templates\",\"About Me\"]), not merged"New value: +"Folder paths to exclude (e.g. Projects; default: daily notes folder, Templates, \"About Me\")"
    • Changedvault_insert_at_anchor1 field changed
      • changedInput schema / properties / content / description
        Previous value: -"Content to insert (one or more lines), inserted verbatim as whole lines — blank lines are kept, and a trailing newline adds a blank line after the block."New value: +"Content to insert (one or more lines)."
    • Changedvault_list_files2 fields changed
      • addedInput schema / properties / extensions / minItems
        Added value: +1
      • changedInput schema / properties / folder / description
        Previous value: -"Folder path to search recursively (e.g. \"attachments\"). Omit to list the whole vault."New value: +"Folder path to search recursively (e.g. \"attachments\" or \"Projects/media\"). Omit to list the whole vault."
    • Changedvault_list_tasks3 fields changed
      • addedInput schema / properties / priority / minItems
        Added value: +1
      • changedInput schema / properties / sort_by / description
        Previous value: -"Sort key (default \"due\"). Date sorts cascade through related fields when the primary is absent; each fallback uses its own natural direction. \"position\" sorts by file path then line number — the natural order for Kanban boards."New value: +"Sort key (default \"due\"). Date sorts cascade through related fields when the primary is absent (through the rest of due → scheduled → start → created, in that order; done does not cascade); each fallback uses its own natural direction. \"position\" sorts by file path then line number — the natural order for Kanban boards."
      • changedInput schema / properties / status / description
        Previous value: -"Status filter, OR-combined (default \"not_done\" = todo + in_progress, excluding done and cancelled). Virtual values expand in arrays: \"not_done\" adds todo + in_progress, \"all\" includes every status."New value: +"Status filter, OR-combined (default \"not_done\" = todo + in_progress, excluding done and cancelled). \"all\" includes every status."
    • Changedvault_memory_recall1 field changed
      • changedInput schema / properties / limit / description
        Previous value: -"Cap on returned entries (default 50). When more match, the least-relevant are dropped and truncated=true — never a date range."New value: +"Cap on returned entries (default 50)."
    • Changedvault_patch_note3 fields changed
      • changedInput schema / properties / content / description
        Previous value: -"Markdown content to insert, written verbatim with no separator added — end it with a newline to leave a blank line after the inserted block. Must not begin with the target heading text (it would duplicate the heading, which is kept automatically)."New value: +"Markdown content to insert. Must not begin with the target heading text (it would duplicate the heading, which is kept automatically)."
      • changedInput schema / properties / heading / description
        Previous value: -"Target heading text (case-sensitive exact match). Required for replace and insert_before. Optional for append/prepend (omit for file-level operation)."New value: +"Target heading text (case-sensitive exact match). Omit for a file-level append or prepend."
      • changedInput schema / properties / operation / description
        Previous value: -"append | prepend | replace | insert_before. replace and insert_before require a heading; append and prepend work with or without one."New value: +"append | prepend | replace | insert_before."
    • Changedvault_read_note3 fields changed
      • changedInput schema / properties / heading / description
        Previous value: -"Return only this section (heading line + body, through the next same-or-higher heading). Case-sensitive exact match."New value: +"Return only this section. Case-sensitive exact match."
      • changedInput schema / properties / limit / description
        Previous value: -"Maximum lines returned (default: all remaining). A paged read's metadata line states the window, the total line count, and the next start_line."New value: +"Maximum lines returned (default: all remaining)."
      • changedInput schema / properties / start_line / description
        Previous value: -"First line to return, 1-based (default 1). Pages the delivered rendition (full body or a heading section). Not valid for outline or properties_only (JSON modes)."New value: +"First line to return, 1-based (default 1). Pages the text output (full body or a heading section). Not valid for outline or properties_only (JSON modes)."
    • Changedvault_replace_span2 fields changed
      • changedInput schema / properties / content / description
        Previous value: -"Replacement content (one or more lines) — replaces every line of the matched span. Must be non-empty; use vault_delete_span to delete without replacement."New value: +"Replacement content (one or more lines) — replaces every line of the matched span. Must be non-empty."
      • changedInput schema / properties / end_anchor / description
        Previous value: -"Short, unique substring that identifies the LAST line of the block, searched at or after the start_anchor line. The entire line is selected. Omit to replace just the single line containing start_anchor."New value: +"Short substring that identifies the LAST line of the block. The entire line is selected. Omit to replace just the single line containing start_anchor."
    • Changedvault_search5 fields changed
      • changedInput schema / properties / filters / description
        Previous value: -"Optional structured filters — all conditions AND-combine with each other and with the text query"New value: +"Optional structured filters"
      • changedInput schema / properties / filters / properties / created / description
        Previous value: -"Created date bounds (YYYY-MM-DD) on the frontmatter \"created\" property — notes without a parseable value never match"New value: +"Created date bounds (YYYY-MM-DD) on the frontmatter \"created\" property"
      • changedInput schema / properties / include_leading_callout / description
        Previous value: -"If true, each result includes its leading_callout ({ type, title, body }) when present. Off by default to keep results lean."New value: +"If true, include each result's leading callout. Off by default to keep results lean."
      • changedInput schema / properties / query / description
        Previous value: -"Search query text — unquoted terms use implicit AND with stemming; wrap in double quotes for exact phrases"New value: +"Search query text"
      • changedInput schema / properties / snippet_tokens / description
        Previous value: -"Snippet length in tokens (default 30)"New value: +"Snippet length in words (default 30)"
    • Changedvault_search_by_folder1 field changed
      • changedInput schema / properties / recursive / description
        Previous value: -"Include subfolders (default: true)"New value: +"Include subfolders (default: true); false lists only the folder's top level"
    • Changedvault_search_by_property1 field changed
      • changedInput schema / properties / value / description
        Previous value: -"Value to match (exact, case-sensitive, e.g. \"active\", \"session-log\"). Use vault_list_property_values to discover valid values for a key."New value: +"Value to match (e.g. \"active\", \"4\", \"1e-7\"). Use vault_list_property_values to discover valid values for a key."
    • Changedvault_update_memory1 field changed
      • changedInput schema / properties / options / properties / date / description
        Previous value: -"ISO YYYY-MM-DD date (defaults to today)"New value: +"ISO YYYY-MM-DD date (defaults to today, server timezone)"
    • Changedvault_update_properties1 field changed
      • changedInput schema / properties / properties / description
        Previous value: -"Properties to merge. New keys are added; existing keys are overwritten; a null value deletes that key; unmentioned keys are preserved."New value: +"Properties to merge; a null value deletes that key."
    • Changedvault_update_task3 fields changed
      • changedInput schema / properties / position / description
        Previous value: -"Where within the target heading the task lands after a heading move or auto-done-lane move. \"top\" or \"bottom\" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike \"bottom\", which appends after any trailing section content). Defaults to \"top\" on heading moves. Without a heading, triggers a same-lane reorder to the given position; omitting position entirely performs no reorder. Ignored when the task is deleted on completion. Not valid on sub-tasks."New value: +"Where within the target heading the task lands. \"top\" or \"bottom\" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike \"bottom\", which appends after any non-task text at the end of the section). Defaults to \"top\" on heading moves."
      • changedInput schema / properties / recurrence / description
        Previous value: -"Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every 2 weeks when done\") to set, or null to remove it. Completing the task spawns its next occurrence."New value: +"Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every month on the 15th\", \"every 2 weeks when done\") to set, or null to remove it. Completing the task spawns its next occurrence."
      • changedInput schema / properties / status / description
        Previous value: -"Target status. \"done\" appends the ✅ date and, on a Kanban board, moves the card and its checklist sub-items to the done lane (sub-item checkboxes are left as they are); a task with 🏁 delete / [onCompletion:: delete] is removed from the file instead. \"cancelled\" appends the ❌ date."New value: +"Target status. \"done\" appends the ✅ date; \"cancelled\" appends the ❌ date."
    • Changedvault_write_note1 field changed
      • changedInput schema / properties / properties / description
        Previous value: -"Optional properties to merge. New keys are added; existing keys with matching names are overwritten; a null value deletes that key; unmentioned keys are preserved from the existing file."New value: +"Optional properties to merge; a null value deletes that key."
  5. 23 tool updatesv0.54.7
    • Changedvault_create_task1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the note (must end in \".md\"). The note must already exist."New value: +"Vault-relative path to the note (must end in \".md\"). The note must already exist. Use the exact letter case."
    • Changedvault_delete_memory2 fields changed
      • addedInput schema / properties / entry / minLength
        Added value: +1
      • changedInput schema / properties / file / description
        Previous value: -"Memory file name without .md (e.g. \"Principles\")"New value: +"Memory file name without .md (e.g. \"Principles\"). Use the exact letter case."
    • Changedvault_delete_note2 fields changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path of the note to delete, including the \".md\" extension"New value: +"Vault-relative path of the note to delete, including the \".md\" extension. Use the exact letter case."
      • changedInput schema / properties / prune_empty_folders / description
        Previous value: -"When true, remove the note's parent folder(s) if deleting it leaves them empty, walking up to (but never including) the vault root. Default false matches Obsidian, which leaves empty folders in place. Only removes a folder with zero entries — a folder still holding any file, including a hidden one like .DS_Store, is left alone."New value: +"When true, also remove parent folders the delete leaves empty. Default false."
    • Changedvault_delete_span1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the note, including the \".md\" extension (e.g. \"Tracker.md\", \"Notes/Plan.md\")"New value: +"Vault-relative path to the note, including the \".md\" extension (e.g. \"Tracker.md\", \"Notes/Plan.md\"). Use the exact letter case."
    • Changedvault_get_daily_note1 field changed
      • addedInput schema / properties / date / minLength
        Added value: +1
    • Changedvault_get_memory1 field changed
      • changedInput schema / properties / file / description
        Previous value: -"Memory file name without .md (e.g. \"Principles\", \"Opinions\")"New value: +"Memory file name without .md (e.g. \"Principles\", \"Opinions\"). Use the exact letter case."
    • Changedvault_insert_at_anchor1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the note, including the \".md\" extension (e.g. \"Notes/Plan.md\", \"Tracker.md\")"New value: +"Vault-relative path to the note, including the \".md\" extension (e.g. \"Notes/Plan.md\", \"Tracker.md\"). Use the exact letter case."
    • Changedvault_list_notes4 fields changed
      • changedInput schema / properties / folder / description
        Previous value: -"Folder path prefix (e.g. \"About Me\", \"Projects\"). Includes all subfolders."New value: +"Vault-relative folder to list (e.g. \"About Me\", \"Projects\")."
      • addedInput schema / properties / folder / minLength
        Added value: +1
      • changedInput schema / properties / glob / description
        Previous value: -"Glob pattern for path filtering (e.g. \"**/*session-log*.md\"). Supports * and ** wildcards. Combined with folder when both are set."New value: +"Glob pattern for note paths (e.g. \"**/*session-log*.md\")."
      • addedInput schema / properties / glob / minLength
        Added value: +1
    • Changedvault_list_property_values2 fields changed
      • changedInput schema / properties / folder / description
        Previous value: -"Restrict to a folder prefix (e.g. \"Projects\")"New value: +"Restrict to a folder (e.g. \"Projects\")"
      • changedInput schema / properties / limit / description
        Previous value: -"Max values to return (default 50). Increase for high-cardinality properties."New value: +"Max values to return (default 50)."
    • Changedvault_list_tasks2 fields changed
      • changedInput schema / properties / folder / description
        Previous value: -"Restrict to a note-path prefix (e.g. \"Code Projects/vault-cortex\")"New value: +"Restrict to a folder (e.g. \"Code Projects/vault-cortex\")"
      • changedInput schema / properties / path / description
        Previous value: -"Restrict to one note (vault-relative path ending \".md\")"New value: +"Restrict to one note (vault-relative path ending \".md\", case-sensitive)"
    • Changedvault_memory_recall1 field changed
      • changedInput schema / properties / file / description
        Previous value: -"Optional: restrict to one memory file, name without .md (e.g. \"Opinions\"). Omit for cross-file recall — the default and usual choice."New value: +"Optional: restrict to one memory file, name without .md (e.g. \"Opinions\"), in its exact letter case. Omit for cross-file recall — the default and usual choice."
    • Changedvault_move_note3 fields changed
      • changedInput schema / properties / new_path / description
        Previous value: -"Destination vault-relative path (e.g. \"Projects/Spec.md\"). Must end in .md and must not already exist; parent folders are created as needed."New value: +"Destination vault-relative path (e.g. \"Projects/Spec.md\"). Must end in .md and must not already exist; parent folders are created as needed. Use the exact letter case of existing folders; a different case can create a second folder."
      • changedInput schema / properties / old_path / description
        Previous value: -"Current vault-relative path of the note to move (e.g. \"Inbox/Draft.md\"). Must end in .md."New value: +"Current vault-relative path of the note to move (e.g. \"Inbox/Draft.md\"). Must end in .md. Use the exact letter case."
      • changedInput schema / properties / prune_empty_folders / description
        Previous value: -"When true, remove the source folder(s) if the move leaves them empty, walking up to (but never including) the vault root. Default false matches Obsidian, which leaves empty folders in place. Only removes a folder with zero entries — an in-place rename or a move into a subfolder of the source leaves it non-empty and prunes nothing."New value: +"When true, also remove parent folders of old_path that the move leaves empty. Default false."
    • Changedvault_patch_note1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the note, including the \".md\" extension (e.g. \"TASKS.md\", \"Projects/plan.md\")"New value: +"Vault-relative path to the note, including the \".md\" extension (e.g. \"TASKS.md\", \"Projects/plan.md\"). Use the exact letter case."
    • Changedvault_read_file4 fields changed
      • changedInput schema / properties / limit / description
        Previous value: -"Maximum lines returned (default: all remaining). A paged read's metadata line states the window, the total line count, and the next start_line. The output byte cap still applies to the window — reduce limit if it overflows."New value: +"Maximum lines returned (default: all remaining)."
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the file, including its extension (e.g. \"attachments/photo.png\", \"Boards/Roadmap.canvas\"). Must NOT end in \".md\" — notes are read with vault_read_note."New value: +"Vault-relative path to the file, including its extension (e.g. \"attachments/photo.png\", \"Boards/Roadmap.canvas\"). Must NOT end in \".md\" — notes are read with vault_read_note. Use the exact letter case."
      • changedInput schema / properties / raw / description
        Previous value: -"Return an alternative representation of the file. For .canvas this is the JSON Canvas source (geometry, ids, colors); for .pdf this renders pages as images instead of extracting text — useful for scanned documents, diagrams, and layout-sensitive content. Text formats already return their source, so raw changes nothing there. Images have no text source — raw returns an error."New value: +"Return the file's alternative form: JSON source for .canvas, page images for .pdf. Changes nothing for text formats, which already return their source. Rejected for images."
      • changedInput schema / properties / start_line / description
        Previous value: -"First line to return, 1-based (default 1). Pages any text result — text formats, canvas outlines and raw JSON, PDF-extracted text. Not valid for images or for PDFs with raw: true."New value: +"First line to return, 1-based (default 1)."
    • Changedvault_read_note1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the note, including the \".md\" extension (e.g. \"About Me/Principles.md\")"New value: +"Vault-relative path to the note, including the \".md\" extension (e.g. \"About Me/Principles.md\"). Use the exact letter case."
    • Changedvault_replace_in_note1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the note, including the \".md\" extension (e.g. \"Projects/plan.md\")"New value: +"Vault-relative path to the note, including the \".md\" extension (e.g. \"Projects/plan.md\"). Use the exact letter case."
    • Changedvault_replace_span1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the note, including the \".md\" extension (e.g. \"Tracker.md\", \"Notes/Plan.md\")"New value: +"Vault-relative path to the note, including the \".md\" extension (e.g. \"Tracker.md\", \"Notes/Plan.md\"). Use the exact letter case."
    • Changedvault_search1 field changed
      • changedInput schema / properties / filters / properties / folder / description
        Previous value: -"Restrict to a folder path prefix (e.g. \"Projects\")"New value: +"Restrict to a folder (e.g. \"Projects\")"
    • Changedvault_search_by_property2 fields changed
      • changedInput schema / properties / folder / description
        Previous value: -"Restrict to a folder prefix (e.g. \"Projects\")"New value: +"Restrict to a folder (e.g. \"Projects\")"
      • changedInput schema / properties / limit / description
        Previous value: -"Max results (default 20). Increase for broad metadata queries."New value: +"Max results (default 20)"
    • Changedvault_update_memory1 field changed
      • changedInput schema / properties / file / description
        Previous value: -"Memory file name without .md (e.g. \"Principles\")"New value: +"Memory file name without .md (e.g. \"Principles\"). Use the exact letter case; a different case can create a second file."
    • Changedvault_update_properties1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the note, including the \".md\" extension"New value: +"Vault-relative path to the note, including the \".md\" extension. Use the exact letter case."
    • Changedvault_update_task1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path to the note containing the task (must end in \".md\")"New value: +"Vault-relative path to the note containing the task (must end in \".md\"). Use the exact letter case."
    • Changedvault_write_note1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Vault-relative path including the \".md\" extension (e.g. \"Projects/notes.md\"). Parent folders are created as needed."New value: +"Vault-relative path including the \".md\" extension (e.g. \"Projects/notes.md\"). Parent folders are created as needed. Use the exact letter case; a different case can create a duplicate note or folder."
  6. 1 tool updatev0.54.1
    • Changedvault_get_memory2 fields changed
      • changedInput schema / properties / on_or_after / description
        Previous value: -"Inclusive date filter (YYYY-MM-DD). When provided with file and section, returns structured JSON entries dated on or after this date instead of raw markdown. Requires both file and section."New value: +"Inclusive date filter (YYYY-MM-DD). When provided with file, returns structured JSON entries dated on or after this date instead of raw markdown. Requires a file; add section to scope to one H2 section."
      • changedInput schema / properties / section / description
        Previous value: -"H2 section heading (e.g. \"Decision heuristics (newest first)\"). Matched case-insensitively, with or without the \"(newest first)\" suffix. Call vault_list_memory_files first to discover valid names."New value: +"H2 section heading (e.g. \"Decision heuristics (newest first)\"). Matched case-insensitively, with or without the \"(newest first)\" suffix. Omit to read the whole file (with on_or_after, every H2 section's entries). Call vault_list_memory_files first to discover valid names."
  7. 2 tool updatesv0.54.0
    • Changedvault_create_task1 field changed
      • changedInput schema / properties / subtasks / description
        Previous value: -"Checklist item descriptions — created as indented [ ] lines under the card (no metadata). For full sub-tasks with dates, priority, and block_id, make a separate call with parent_block_id."New value: +"Checklist item descriptions — created as indented todo lines under the card (no metadata). For full sub-tasks with dates, priority, and block_id, make a separate call with parent_block_id."
    • Changedvault_update_task1 field changed
      • changedInput schema / properties / add_subtasks / description
        Previous value: -"Checklist items to append, one indented [ ] line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id."New value: +"Checklist items to append, one indented todo line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id."
  8. 4 tool updatesv0.53.0
    • Changedvault_create_task4 fields changed
      • addedInput schema / properties / position / anyOf
        Added value: +[
        +  {
        +    "enum": [
        +      "top",
        +      "bottom"
        +    ],
        +    "type": "string"
        +  },
        +  {
        +    "maximum": 9007199254740991,
        +    "minimum": 1,
        +    "type": "integer"
        +  }
        +]
      • changedInput schema / properties / position / description
        Previous value: -"Where within the heading section the task is placed. Defaults to bottom. Kanban boards with new-card-insertion-method set to prepend default to top instead. Ignored when no heading or when placing under a parent."New value: +"Where within the heading section the task is placed. \"top\" or \"bottom\" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike \"bottom\", which appends after any trailing section content). Defaults to bottom. Kanban boards with new-card-insertion-method set to prepend default to top instead. Ignored when no heading or when placing under a parent."
      • removedInput schema / properties / position / enum
        Removed value: -[
        -  "top",
        -  "bottom"
        -]
      • removedInput schema / properties / position / type
        Removed value: -"string"
    • Changedvault_get_memory1 field changed
      • addedInput schema / properties / on_or_after
        Added value: +{
        +  "description": "Inclusive date filter (YYYY-MM-DD). When provided with file and section, returns structured JSON entries dated on or after this date instead of raw markdown. Requires both file and section.",
        +  "minLength": 1,
        +  "type": "string"
        +}
    • Changedvault_read_note1 field changed
      • changedInput schema / properties / outline / description
        Previous value: -"If true, returns { leading_callout?, leading_content?, headings } as JSON instead of body content — a cheap structure fetch for large notes. headings: [{ level, text, bytes }]; leading_callout: { type, title, body } when the note has a top-of-file callout; leading_content: the rest of the body text above the first heading (callout lines excluded) when the note has any."New value: +"If true, returns { bytes, modified, leading_callout?, leading_content?, headings } as JSON instead of body content — a cheap structure fetch for large notes. headings: [{ level, text, bytes }]; leading_callout: { type, title, body } when the note has a top-of-file callout; leading_content: the rest of the body text above the first heading (callout lines excluded) when the note has any."
    • Changedvault_update_task5 fields changed
      • addedInput schema / properties / position / anyOf
        Added value: +[
        +  {
        +    "enum": [
        +      "top",
        +      "bottom"
        +    ],
        +    "type": "string"
        +  },
        +  {
        +    "maximum": 9007199254740991,
        +    "minimum": 1,
        +    "type": "integer"
        +  }
        +]
      • removedInput schema / properties / position / default
        Removed value: -"top"
      • changedInput schema / properties / position / description
        Previous value: -"Where within the target heading the task lands after a heading move or auto-done-lane move. Defaults to \"top\". Ignored when no heading move occurs."New value: +"Where within the target heading the task lands after a heading move or auto-done-lane move. \"top\" or \"bottom\" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike \"bottom\", which appends after any trailing section content). Defaults to \"top\" on heading moves. Without a heading, triggers a same-lane reorder to the given position; omitting position entirely performs no reorder. Ignored when the task is deleted on completion. Not valid on sub-tasks."
      • removedInput schema / properties / position / enum
        Removed value: -[
        -  "top",
        -  "bottom"
        -]
      • removedInput schema / properties / position / type
        Removed value: -"string"
  9. 2 tool updatesv0.51.1
    • Changedvault_create_task1 field changed
      • addedInput schema / properties / on_completion
        Added value: +{
        +  "description": "Tasks plugin 🏁 onCompletion action. \"delete\" removes the task line on completion; \"keep\" leaves it in place.",
        +  "enum": [
        +    "delete",
        +    "keep"
        +  ],
        +  "type": "string"
        +}
    • Changedvault_update_task3 fields changed
      • changedInput schema / properties / add_subtasks / description
        Previous value: -"Checklist items to append, one indented [ ] line each, under the task's existing items — never replaces them. Can be combined with any other change. For full sub-tasks with metadata, use vault_create_task with parent_block_id."New value: +"Checklist items to append, one indented [ ] line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id."
      • addedInput schema / properties / on_completion
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "delete",
        +        "keep"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Tasks plugin 🏁 onCompletion action to set, or null to remove it. \"delete\" removes the task line on completion; \"keep\" leaves it in place (which is also the behavior when no 🏁 field exists on the task). Omitting this parameter leaves the field unchanged."
        +}
      • changedInput schema / properties / status / description
        Previous value: -"Target status. \"done\" appends the ✅ date and, on a Kanban board, moves the card and its checklist sub-items to the done lane (sub-item checkboxes are left as they are). \"cancelled\" appends the ❌ date."New value: +"Target status. \"done\" appends the ✅ date and, on a Kanban board, moves the card and its checklist sub-items to the done lane (sub-item checkboxes are left as they are); a task with 🏁 delete / [onCompletion:: delete] is removed from the file instead. \"cancelled\" appends the ❌ date."
  10. 2 tool updatesv0.51.0
    • Changedvault_create_task1 field changed
      • addedInput schema / properties / recurrence
        Added value: +{
        +  "description": "Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every 2 weeks when done\"). Completing the task spawns its next occurrence.",
        +  "minLength": 1,
        +  "type": "string"
        +}
    • Changedvault_update_task1 field changed
      • addedInput schema / properties / recurrence
        Added value: +{
        +  "anyOf": [
        +    {
        +      "minLength": 1,
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every 2 weeks when done\") to set, or null to remove it. Completing the task spawns its next occurrence."
        +}

TDQS

A4.5/5.0

Scored across 33 tools

Disambiguation4/5

Nearly every description explicitly names its siblings and states when to prefer one over another (vault_search vs vault_search_by_tag vs vault_search_by_property vs vault_search_by_folder; vault_read_note vs vault_read_file vs vault_get_memory), which makes the surface navigable despite its size. The weak spot is the body-editing family — vault_patch_note, vault_replace_in_note, vault_replace_span, vault_delete_span, and vault_insert_at_anchor all write into the same note region and differ by anchor/heading strategy, so an agent must read several descriptions before picking one.

Naming Consistency5/5

Every tool is namespaced with vault_ and uses snake_case verb_noun form (vault_read_note, vault_list_tasks, vault_update_properties, vault_search_by_tag). The only deviations are the noun-first vault_memory_recall and vault_recent_notes, which are still readable and unambiguous within the scheme.

Tool Count3/5

33 tools is above the comfortable range, and the count is driven less by distinct capabilities than by granular editing primitives — five separate span/anchor/anchor-insert tools where one or two parameterized tools would likely suffice. The breadth of domain (notes, files, search, memory, tasks, graph, daily notes) justifies a large surface, but consolidation would clearly help.

Completeness5/5

Lifecycle coverage is unusually thorough: full CRUD plus move-with-backlink-rewrite for notes, dedicated memory read/append/delete with discovery, task create/update/list with structured metadata, graph traversal in both directions, orphan detection, property and tag schema discovery, daily notes, and non-markdown file reading. The remaining gaps (writing non-markdown files, canvas authoring, inline #tag search) are explicitly documented as out of scope rather than dead ends.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    A server that enables AI agents to perform sophisticated knowledge discovery and analysis across Obsidian vaults through the Local REST API plugin, supporting complex multi-step workflows with advanced filtering and full content retrieval.
    3
    21
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A third-party MCP server for interacting with HashiCorp Vault to manage ACL policies, audit devices, and secret engines like KV v2, PKI, and Transit. It provides tools for system backend administration and includes prompts for generating security policy configurations.
    MIT