Vault Cortex Obsidian MCP Server
Vault Cortex is an MCP server that gives AI agents secure read/write access to an Obsidian vault, with hybrid search, task management, structured memory, link-graph navigation, file reading, and daily notes.
Read and navigate notes — read full notes, properties only, heading outlines, or specific sections with line paging; list notes by folder or glob.
Write and edit notes — create or overwrite notes, patch by heading, find-and-replace, insert/delete/replace anchored line spans, delete notes to trash, and move/rename notes while rewriting links vault-wide.
Manage properties — read, merge, replace, or repair frontmatter properties without touching the body.
Search and discover — hybrid keyword + semantic search with filters for folder, tags, properties, dates, and related links; search by tag, folder, or property; browse recent notes, tags, property keys, and property values.
Manage tasks — list vault-wide tasks with Kanban-aware filters and sorting; create tasks with dates, priority, recurrence, dependencies, block IDs, and subtasks; update, complete, move, or reorder tasks, including recurring-task spawning and on-completion deletion.
Use structured memory — list, read, append, and delete dated entries in
About Me/memory files; perform entry-granular hybrid recall across all memory files and time.Explore the link graph — get backlinks, outgoing links, broken-link detection, and orphan notes.
Read non-markdown files — view images (auto-downscaled), canvases as outlines or raw JSON, PDFs as structured text or rendered pages, and text/data files with paging; list non-markdown files by extension.
Access daily notes — retrieve today's or any date's daily note using the vault's configured folder and filename format.
Operate safely — supports read-only mode, per-tool disabling, protected paths, hidden-path blocking, atomic writes, per-file locking, trash retention, and OAuth 2.1 or static bearer authentication.
Provides full access to an Obsidian vault, allowing AI agents to search, read, write, manage structured memory, query backlinks, and work with daily notes.
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
.mdfiles 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 initThat'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 upFull 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 | |
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 |
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 remoteThat'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 -dLeft 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 |
|
Remote (one-click) |
|
Remote (self-hosted) |
|
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| ServerSee ARCHITECTURE.md for the full design, auth flow diagrams, and component breakdown.
Hybrid Search
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_recallretrieves 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 whatGrows 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 boardUpdate — 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: trueto render pages as images instead, showing layout, diagrams, and tables that text extraction can't preserve — scanned and image-only PDFs work in this modeText 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 |
| Read a note — full body, properties, outline, or a section |
| Create a note (fails if it already exists; set | |
| Heading-targeted edit (append, prepend, replace with | |
| Find-and-replace text in a note (first match or | |
| Delete a block of lines by short anchors, no full re-quote | |
| Replace a block of lines by short anchors with new content | |
| Insert content before or after a line identified by a short anchor | |
| List notes with optional glob/folder filter | |
| Delete a note, honoring the vault's trash setting (protected paths enforced) | |
| Move or rename a note, rewriting links across the vault | |
Search |
| Hybrid search with tag/folder/property/date filters |
| Find notes by tag (exact or prefix match) | |
| Browse notes in a folder with metadata | |
| Recently modified or created notes | |
| All tags with usage counts | |
Tasks |
| Vault-wide task index with sub-task depth — Kanban-aware, date/priority/heading filters |
| Create a correctly-formatted task — dates, priority, recurrence, on_completion, sub-tasks, block_id | |
| Edit any task field in one call — completing a recurring task creates its next occurrence | |
Memory |
| Read structured memory (file, section, or all) |
| Append a dated entry to a memory section | |
| Remove a specific memory entry by date | |
| Discover memory files, their sections, and each file's entry policy | |
| Entry-granular hybrid recall of a topic across memory files, oldest-first | |
Properties |
| All property keys with sample values |
| Distinct values for a property key | |
| Find notes by property key-value | |
| Add, update, or replace properties without touching the body | |
Links |
| Notes linking to a given path |
| Links from a given note | |
| Notes with no incoming links | |
Files |
| Read a non-markdown file — images delivered as images, canvases as readable outlines |
| Browse the vault's non-markdown files with sizes and per-extension counts | |
Daily Notes |
| 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 |
| — | 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 |
|
| 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 |
|
| 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 |
| Display name in search results; falls back to the filename when missing |
| Search and filter by tag, including parent-child hierarchies ( |
| Filter by note type — |
| Sort by creation date and see when each note was created alongside every search result |
| 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 |
| Yes | — | Bearer token for authentication (also the JWT signing key) |
| Local only | — | Host path to your vault (bind mount source; remote uses a named volume). Must not contain |
| Remote only | — | Public URL for OAuth discovery metadata. Filled in automatically on Render and Railway (from |
| — | — | Obsidian Sync auth token. Leave empty to sign in through the |
| Remote only | — | Exact name of your Obsidian vault (case-sensitive) |
| Remote only | — | End-to-end encryption password, if your vault has one. Leave empty otherwise. |
| — | — | 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 |
| — |
| Set |
| — |
| Cross-encoder reranking mode: |
| — |
| Set |
| — |
| Set |
| — |
| Set |
| — | — | Hide individual tools by name, comma-separated (e.g. |
| — |
| Vault folder for structured memory files |
| — |
| Folders that |
| — | daily notes folder, | Folders excluded from orphan detection. The daily notes folder is read from |
| — | from | Sets the folder your daily notes live in. When unset, read from |
| — | from | Sets the daily note filename format — same tokens as Obsidian's daily note date format setting. When unset, read from |
| — |
| IANA timezone for timestamps and daily note resolution |
| — | GitHub repo URL | URL returned in OAuth discovery metadata |
| — |
| Logging verbosity: |
| — |
| Directory for log files that survive container re-creation. The container's own log (what |
| — |
| Days to keep log files before automatic cleanup on startup; only applies when |
| — |
| On Windows? Set |
| — |
| Maximum file size |
| — |
| Byte budget for images delivered by |
| — |
| Maximum PDF pages to render as images when |
| Local only |
| Days a note deleted under Obsidian's default "Move to system trash" setting stays in |
| — |
| Number of trusted reverse-proxy hops used to derive the client IP from |
| — |
| How many trailing |
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_CONFIGSsetting 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 rootDAILY_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.jsonis available in the server's vault.Apply environment changes with the CLI's
restartcommand, 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. Whenapp.jsonisn'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_notereturns a clear error — change the format in Obsidian or setDAILY_NOTES_FORMATto 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, avault_write_noteoverwrite, 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 ofkey: valueproperties, 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 afterTRASH_RETENTION_DAYS(default 30 days;nonekeeps 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=truehides 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 |
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:
Your client registers automatically and receives a client ID and secret.
Enter your
MCP_AUTH_TOKENon the browser consent page to approve access.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 | |
Remote · one-click | Render or Railway — one persistent volume, no server to manage | |
Remote · self-hosted | VPS + Obsidian Sync — access from any device | |
Remote · AWS (SST) | IaC reference deployment — automated infra, defense-in-depth auth |
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
:remoteimage 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 buildnpm 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 tokenSee 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-vaultRoadmap
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
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 toolsvault_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, with its created date (➕) set to today; set in_progress later with vault_update_task. Override format only at the user's request.
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.
position: ignored without a heading, and under a parent, where the sub-task goes after the parent's last sub-item.
recurrence: a rule ending "when done" bases the next occurrence on the completion day; a task with no dates recurs as a dateless copy.
Errors: (messages use camelCase names, e.g. blockId for block_id)
"note not found" — path does not exist; check the path with vault_list_notes
"path must end in …" — add the .md extension
"absolute path blocked" / "path traversal blocked" / "hidden path blocked" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it
"heading required for Kanban boards" — pass heading with the target lane
"heading "X" not found; available: ..." — the message lists the note's headings
"cannot place at position P under "X" — the heading appears N times" — integer position on a note with duplicate heading names; use "top" or "bottom" (they place under the first match), or rename one section to make it unique
"parent task not found" — parent_block_id or parent_line doesn't resolve to a task outside a fenced code block or %% %% comment; re-read the parent's block_id or line with vault_list_tasks
"parent task ambiguous" — more than one task line ends in that ^id (the message lists their line numbers); pass parent_line instead
"checkbox "[c]" is a NON_TASK status" / "no checkbox symbol for status ..." — in the Tasks plugin's status settings, the parent's checkbox character (c) is typed NON_TASK 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" — pick a block_id no other line in the note ends in; the invalid-characters message names the allowed characters
"blockId is required" — pass block_id
"blockId is not allowed on a sub-task on a Kanban board" / "subtask ... ends in a block ID" — the message names what to drop
"description is empty" / "subtasks cannot contain an empty item" / "description must be a single line" / "subtasks items must be a single line" — pass visible text on a single line
"taskId ... contains invalid characters" / "dependsOn entry ... contains invalid characters" — the message names the allowed characters
"unrecognized recurrence rule ..." — the message gives example rules
"invalid date: …" — the message names the expected format
"concurrent write in progress" — another write to this note is in flight; retry
"ENAMETOOLONG: …", "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. 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; block_id is omitted when the task has none (line is then its handle); heading is the nearest heading above the new task (omitted when the note has none); subtasks lists each checklist item written as { line, description } (omitted when none); changes lists every field written as "field: (none) → value", and checklist items as "subtasks: 0 → N"; advisories (omitted when the line round-trips clean) lists one sentence per place the stored line parses back differently than submitted — see Obsidian syntax above.
| Name | Required | Description | Default |
|---|---|---|---|
| due | No | Deadline (📅), YYYY-MM-DD, calendar-validated. Omit when there is no deadline. | |
| path | Yes | Vault-relative path to the note (must end in ".md"). The note must already exist. Use the exact letter case. | |
| start | No | Earliest day work can begin (🛫), YYYY-MM-DD, calendar-validated. | |
| format | No | Field format. Default: auto-detected from .obsidian/ config, falling back to emoji. | |
| heading | No | Target heading's text, without the # marks, matched exactly. On a regular note, omit to append at end of body. | |
| task_id | No | Tasks plugin 🆔 identifier other tasks can name in depends_on — letters, digits, hyphens, and underscores; separate from block_id. | |
| block_id | No | The ^block-id (without the ^) for stable identification — letters, digits, and hyphens only ([a-zA-Z0-9-]+), unique within the note. | |
| position | No | 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"). | |
| priority | No | Priority signifier: highest 🔺, high ⏫, medium 🔼, low 🔽, lowest ⏬. Omit for normal priority — no signifier is written. | |
| subtasks | No | 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. | |
| scheduled | No | Day the work is planned for (⏳), YYYY-MM-DD, calendar-validated. | |
| depends_on | No | Tasks plugin ⛔ dependency IDs (🆔 values of other tasks), not checked against existing tasks. Omit when there are no dependencies. | |
| recurrence | No | 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. | |
| description | Yes | The task text (before metadata fields). | |
| parent_line | No | 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. | |
| on_completion | No | Tasks 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_id | No | ^block-id (without the ^) of an existing task to nest under as a sub-task. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only provide the broad write profile (readOnlyHint=false, destructiveHint=false, idempotentHint=false, openWorldHint=false). The description adds substantial behavior: tasks are always created as todo with created date set to today, format should only be overridden at the user's request, and it documents return fields, advisories, and a large set of failure modes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with purpose, examples, and when-to-use guidance, then moves into parameters, errors, syntax, and return values. It is long, and the exhaustive error catalog is more than many agents will need, but the length is largely justified by the 17-parameter surface area.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 fully documents the JSON return shape, including path, line, description, block_id, heading, subtasks, changes, and advisories. Combined with the error catalog and Obsidian syntax note, an agent has enough context to invoke the tool correctly and interpret the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema description coverage is already 100%, the description adds cross-parameter semantics that the schema does not cover: heading is required on Kanban boards unless a parent is given, parent_block_id and parent_line are mutually exclusive with heading, block_id is required except on Kanban sub-tasks where it is refused, and position is ignored without a heading or under a parent.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Create a correctly-formatted task in one call.' It immediately distinguishes this from sibling tools by naming vault_update_task and vault_list_tasks, and the examples make the exact scope concrete.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It has an explicit 'When to use' section and routes lightweight checklist items to vault_update_task's add_subtasks param. It also explains when to use vault_update_task later for in_progress status, so both the positive and alternative paths are covered.
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 EntryADestructive
Delete a single dated entry from a memory file in About Me/.
Example: vault_delete_memory({ file: "Opinions", section: "Tools and workflows (newest first)", date: "2026-05-01", entry: "Prefer X over Y" }) Example: vault_delete_memory({ file: "Routines", section: "Daily/weekly rhythm (newest first)", date: "2026-03-10", entry: "Gym Tue/Thu at 7am" }) — prune an expired entry from an entry-policy: living file
When to use: Removing an entry that was wrong when it was written. Memory files are append-only by default, so do NOT delete to reflect a change: append the new state via vault_update_memory instead. The exception is a file whose 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 whole notes; vault_delete_note refuses protected paths.
Parameters:
date + entry together pick the bullet line within the given section; identical copies make the match ambiguous. The entry text matches the bullet's first line only, and the delete also removes the lines that continue that entry.
section scopes the match — an identical entry under a different heading is not found.
Errors:
"memory file not found" — call vault_list_memory_files to discover valid names.
"memory file must not start with a dot" / "memory file must be a bare name without path separators" / "memory file must be named without .md" — each message names the part of the name to drop
"date must be YYYY-MM-DD" — pass the entry's date exactly as the file shows it, zero-padded.
"section not found: …" — no H2 heading matches; the message lists the file's sections
"no entry matching …" — verify the exact text with vault_get_memory(file, section).
"ambiguous: N entries match …" — remove the extra copy with vault_delete_span (first_match: true) or a manual edit, then retry.
"refusing memory write: … would shrink content" — the delete would remove more than half the file; re-read it with vault_get_memory, and remove an entry that really is that large with vault_delete_span or a manual edit.
"cannot read memory file …" — the file can't be read: check file against vault_list_memory_files and shorten an over-long name; otherwise ask the vault's owner to fix its permissions
"ENAMETOOLONG: …", "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 → ## ".
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | ISO YYYY-MM-DD date of the entry (e.g. "2026-05-01"). | |
| file | Yes | Memory file name without .md (e.g. "Principles"). Use the exact letter case. | |
| entry | Yes | Exact entry text, without the "- **YYYY-MM-DD**: " prefix or bullet. | |
| section | Yes | H2 section heading containing the entry. Matched case-insensitively, with or without the "(newest first)" suffix. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true, but the description adds real behavioral detail beyond them: continuation lines under the entry are also removed, identical copies make the match ambiguous, and a guard refuses deletes that would shrink the file by more than half. It also enumerates the concrete error strings and their remedies, including path-name and permissions failures.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and examples are front-loaded, then when-to-use, parameters, errors and return value in a predictable order. It is long — the ten-entry error catalogue is more than most agents need and repeats what runtime messages would say — but each section is scannable and the two examples clarify the ambiguous date+entry pairing.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, but the description supplies a Returns line ('Deleted entry from About Me/<file>.md → ## <section>'). Combined with the usage policy, matching rules and error recovery, an agent has everything needed to invoke this correctly and to recover from failure.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, and the description genuinely adds matching semantics the schema lacks: date+entry jointly select the bullet, entry matches only the bullet's first line, and section scopes the match so an identical entry under another heading is not found. It stops short of restating the naming/format rules for `file` and `date` in prose, but the schema covers those adequately.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource with scope: 'Delete a single dated entry from a memory file in About Me/.' The two worked examples show the exact call shape, and the description explicitly separates this from vault_update_memory, vault_delete_note and vault_delete_span, so an agent can pick it 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use ('removing an entry that was wrong when it was written'), explicit when-NOT ('memory files are append-only by default, so do NOT delete to reflect a change: append via vault_update_memory'), a named exception (files declaring `entry-policy: living`), a prerequisite (call vault_get_memory first) and the alternative for whole notes (vault_delete_note). 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_delete_noteDelete NoteADestructive
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; 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" — ask the vault owner to repair it, then retry
"cannot read daily notes config from .obsidian/daily-notes.json" — 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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path of the note to delete, including the ".md" extension. Use the exact letter case. | |
| prune_empty_folders | No | When true, also remove parent folders the delete leaves empty. Default false. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Although annotations declare destructiveHint=true and readOnlyHint=false, the description adds substantial behavior the annotations cannot convey: the vault's 'Deleted files' setting determines whether the note goes to trash or is permanently deleted, retention rules apply, links break, protected paths are refused, and pruning is best-effort. It also documents concrete error messages and recovery steps, which is unusually transparent for a destructive tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-structured, front-loading the core action before examples, usage guidance, behavior, parameters, errors, and returns. The extensive error catalogue is justified for a destructive filesystem operation, though the entry is verbose enough that an agent must scan rather than absorb it in one glance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers outcomes, trash behavior and retention, protected paths, link breakage, pruning semantics, path-format errors, configuration failures, and the confirmation message. With no output schema, it supplies the return-value detail needed, and it is complete enough for an agent to call the tool safely despite its destructive nature.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3, but the description adds meaningful semantics beyond the schema: prune_empty_folders keeps a folder that contains any file, even a hidden .DS_Store, and pruning failures never fail the call. The examples also reinforce the vault-relative path format and optional pruning behavior.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: delete a markdown note, and explains the two possible outcomes (move to .trash/ or permanent removal). It explicitly distinguishes the tool from vault_delete_memory, vault_move_note, and vault_write_note, so an agent can identify its exact role among siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states exactly when to use it (removing a note no longer needed) and names alternatives for adjacent tasks: vault_delete_memory for memory entries, vault_move_note for relocation, and vault_write_note with overwrite for replacement. It also advises using vault_get_backlinks before deletion when link breakage matters.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_delete_spanDelete SpanADestructive
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 start_anchor and end_anchor match the same line, only that one line is deleted. A span that includes a code block's opening fence can't end at a plain closing fence (```): the end anchor is ambiguous, and first_match: true would end the span at the opening fence; to remove such a block, use vault_replace_in_note with new_text: "".
Behavior: 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 body line holds the fragment where it is searched; 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 "…".
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path to the note, including the ".md" extension (e.g. "Tracker.md", "Notes/Plan.md"). Use the exact letter case. | |
| end_anchor | No | Short substring of the block's LAST line, a body line (case-sensitive). Omit to delete just the single line containing start_anchor. | |
| first_match | No | If an anchor matches more than one line, delete using the first match instead of erroring (default: false — ambiguity is an error). | |
| start_anchor | Yes | Short, unique substring of the block's first line, a body line (case-sensitive). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare destructiveHint=true, but the description adds substantial behavioral context: properties are preserved, CRLF is normalized to LF, blank-line collapse rules are described in detail, same-line anchors delete one line, code-block fence ambiguity is explained, and concurrent-write retry behavior is included. This goes well beyond what the annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but appropriately sized for a destructive editing tool with nuanced anchor and line-break behavior. It is front-loaded with the core purpose, then structured into Examples, When to use, Parameters, Behavior, Errors, and Returns, with no obviously redundant section.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given four parameters, destructive annotations, no output schema, and complex anchor semantics, the description is complete. It explains the return value, error recovery paths, edge cases, and behavioral guarantees an agent needs to invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but the description still adds meaning beyond the schema: end_anchor is searched at or after the start line and must be unique among those lines, same-line anchors delete only that line, first_match changes ambiguity handling, and the code-block fence caveat explains a non-obvious anchor constraint. These are useful semantics the schema alone does not provide.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource — deleting a contiguous block of whole lines from a note by anchor substrings — and explicitly distinguishes the tool from vault_replace_in_note and vault_replace_span. An agent can tell exactly what this tool does and how it differs 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit when-to-use guidance ('Removing a block you have already read — a table row, callout, or run of list items') and names the alternatives: vault_replace_in_note for small in-place edits and vault_replace_span for block replacement. It also clarifies a when-not-to-use case ('this tool only deletes').
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_find_orphansFind OrphansARead-onlyIdempotent
Find notes with no incoming links from other notes or canvases. A note that only links to itself still counts as an orphan.
Example: vault_find_orphans({}) Example: vault_find_orphans({ exclude_folders: ["Templates", "Archive"], limit: 20 })
When to use: Vault maintenance — surfacing notes to integrate into the graph. Link an orphan by mentioning it from a relevant note with vault_patch_note. Prefer vault_get_backlinks to check the connectivity of one specific note rather than scanning the whole vault.
Parameters:
exclude_folders replaces the defaults, it does not add to them — list a default yourself to keep it. Pass [] for no exclusions. Each entry names a whole folder, subfolders included ("Projects" 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.
limit applies after exclusions and sorting; nothing in the response signals truncation.
Behavior: A file change takes a few seconds to appear here, so a link written moments ago may not count yet.
Errors:
An empty array means no orphans were found (after exclusions), not an error.
"too many excluded folders" — pass at most 994 exclude_folders entries, then retry.
"[SqliteError]: …" / "[IndexIntegrityError]: …" — the 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 (null when missing or not an ISO date), 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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max results (default 50) | |
| exclude_folders | No | Folder paths to exclude (e.g. Projects; default: the daily notes folder, "Templates", "About Me") |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description discloses indexing delay, exclusion semantics, truncation behavior, and specific error cases including SQLite/index integrity failures. This provides rich operational context for a read-only, idempotent tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-structured and front-loaded with purpose, examples, usage guidance, parameters, behavior, errors, and returns. The density is justified by the tool's nuanced exclusion and error behavior, with no obvious filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description still fully explains the returned JSON array, field meanings, conditional fields, and sort order. Combined with usage guidance and error handling, it gives an agent everything needed to call and interpret the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 100%, the description adds substantial meaning: exclude_folders replaces defaults, requires re-listing defaults, applies folder-wide and case-insensitively, and affects results but not incoming links. It also clarifies that 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.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: finding notes with no incoming links from other notes or canvases. It also clarifies the edge case that a self-linking note still counts as an orphan, making the tool's scope unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit when-to-use scenario (vault maintenance) and names the alternative vault_get_backlinks for checking one note's connectivity. It also explains how to resolve an orphan via vault_patch_note, so the agent knows both selection and follow-up.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_get_backlinksGet BacklinksARead-onlyIdempotent
Find all notes and canvases that link to a given note or canvas — captures [[wikilinks]], markdown, ![[embeds]], wikilinks inside frontmatter properties (e.g. related:), and canvas cards that embed the file. Heading anchors ([[note#heading]]) and aliases ([[note|alias]]) resolve as backlinks to the base note. Links inside code blocks are ignored; a note linking to itself appears in its own backlinks.
Example: vault_get_backlinks({ path: "Projects/vault-cortex.md" }) Example: vault_get_backlinks({ path: "Diagrams/architecture.canvas" })
When to use: Understanding what references a note or canvas, assessing its connectivity before editing or deleting, or finding related notes via the graph. For outgoing links (what a note links TO), use vault_get_outgoing_links. To find notes nothing else links to, use vault_find_orphans.
Parameters:
path: pass the target's own path, never a link's text: a link written as [[Note]], as a relative ../Note.md, or as a full path counts toward the note it resolves to.
Returns: JSON with path (the queried note or canvas), backlinks (array of { path, title, bytes } sorted by title; bytes is the source's on-disk size), and count.
Errors:
"path must end in …" — add the .md or .canvas extension
A path nothing links to returns an empty result (count 0), not an error; links from a note written or changed in the last few seconds may not appear yet. If you expected links, find valid paths with vault_search, vault_list_notes, or vault_list_files.
"[SqliteError]: …" — the search index failed, not the call; the message says what to do
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Exact vault-relative path including .md or .canvas extension (e.g. "Projects/vault-cortex.md"). Case-sensitive. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, non-destructive, but the description goes well beyond them: it discloses that links inside code blocks are ignored, that self-links appear in their own backlinks, that heading anchors and aliases resolve to the base note, and that recently written notes may not appear yet due to indexing latency. It also classifies errors into caller-caused versus index-failure categories, which annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and link-type coverage, then usage, parameters, returns, and errors in a scannable order. Slightly long, and the Returns section partly restates what could be inferred, but every section carries actionable information for a single-parameter tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite no output schema, the description documents the return shape (path, backlinks array of {path, title, bytes} sorted by title, count), the empty-result-vs-error distinction, indexing latency, and remediation via vault_search/vault_list_notes. An agent can call this correctly and interpret both success and failure without further information.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description earns above baseline by clarifying that path is the target's own path, not a link's text, and explaining that link forms (bare [[Note]], relative ../Note.md, full path) all count toward the resolved note. It does not repeat the case-sensitivity/extension rule, which the schema already handles.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Find all notes and canvases that link to a given note or canvas') and immediately enumerates the link forms it captures — wikilinks, markdown links, embeds, frontmatter properties, canvas cards. This is far more specific than the title and lets an agent distinguish it from outgoing-link and orphan-finding siblings 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'When to use' section names concrete tasks (assessing connectivity before editing/deleting, graph exploration) and explicitly routes alternatives: vault_get_outgoing_links for outgoing links, vault_find_orphans for unlinked notes. When and when-not are both covered.
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 NoteARead-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. For recent vault activity, vault_recent_notes lists the most recently changed notes; it has no date filter.
Parameters:
date: always YYYY-MM-DD, whatever filename format the vault uses; past and future dates are both valid.
Errors:
"invalid date" — use YYYY-MM-DD format
"… is not a calendar date" — the date 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; the message names the tokens and the fix
"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 — the note's full text, properties block included, or null when the note doesn't exist), and exists (boolean). When exists is false, create the note with vault_write_note using the returned path.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | YYYY-MM-DD (e.g. "2026-05-13", "2025-12-31"). Defaults to today in the server's timezone. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, yet the description still adds real value: the three-level config resolution chain (env vars → .obsidian/daily-notes.json → defaults), the specific error strings and their fixes, and the non-existence behavior. The only gap is that rate/perf characteristics aren't mentioned, which is minor for a local read.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then examples, when-to-use, params, errors, returns — a clean structure. The error enumeration is long but each entry names an actionable cause; slightly verbose for a one-parameter read tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 fully specifies the return shape (path/content/exists) including the null-content case and the follow-up action (vault_write_note). Combined with the error catalog and config precedence, 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and already states the YYYY-MM-DD format and the today-default, so the baseline is 3. The description adds meaning beyond the schema by clarifying the input format is always ISO regardless of the vault's configured filename format, and that past and future dates are both accepted.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource+scope: 'Read a daily note by date, using the vault's configured Daily Notes folder and filename date format.' This clearly separates it from the generic path-based vault_read_note sibling, and the examples make the unit of work unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
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 their selection conditions: use vault_patch_note with the returned path to append, and vault_recent_notes for recent activity because it 'has no date filter.' An agent has no ambiguity about routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_get_memoryGet MemoryARead-onlyIdempotent
Read semantic memory from About Me/ files. Each file holds dated bullet entries under H2 headings. With file: that file. With file+section: just that H2 section's entries. No args: every file, joined by --- lines — can be large.
With file+on_or_after: only the entries dated on or after that date, in document order (newest first by default). 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. Prefer vault_read_note for reading non-memory notes. To find entries by topic across every file, use vault_memory_recall.
Errors:
"section requires a file" / "on_or_after requires a file" — add file
"memory file not found" — call vault_list_memory_files to discover valid names
"memory file must not start with a dot" / "memory file must be a bare name without path separators" / "memory file must be named without .md" — each message names the part of the name to drop
"section not found: …" — no H2 heading matches; the message lists the file's sections
"date must be a real ISO calendar date" — on_or_after must be a valid YYYY-MM-DD date
"cannot read memory file …" / "cannot list memory folder …" — a memory file, or the About Me/ folder itself on a call without file, can't be read: check file against vault_list_memory_files and shorten an over-long name; otherwise ask the vault's owner to check the folder
Returns: Without on_or_after, raw markdown with the properties block stripped, or an empty string when no memory files exist yet. With on_or_after, JSON { entries, total, on_or_after } where each entry is { file, section, date, text } — text is the raw entry markdown (bullet + continuation lines, wikilinks intact). An empty match returns { entries: [], total: 0, on_or_after }.
| Name | Required | Description | Default |
|---|---|---|---|
| file | No | Memory file name without .md (e.g. "Principles", "Opinions"). Use the exact letter case. | |
| section | No | H2 section heading (e.g. "Decision heuristics (newest first)"). Matched case-insensitively, with or without the "(newest first)" suffix. | |
| on_or_after | No | Inclusive date filter (YYYY-MM-DD). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (readOnly, idempotent, non-destructive, closed-world), yet the description still adds real behavior: output shape flips from raw markdown to JSON when on_or_after is present, the properties block is stripped, empty results return an empty string, and the no-arg call "can be large." The error catalog documents constraint failures and what each message means, which the annotations do not.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Behavior is front-loaded and the examples are genuinely useful, but there is redundancy: the section-scoping behavior is stated in the header line and then restated in prose ("Add section to scope to one H2 section; omit it to read every H2 section's entries..."). The long error list is valuable but pushes the description toward the upper end of useful length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 return values — and it does, describing both the markdown and JSON return shapes down to the entry fields ({ file, section, date, text }) and the empty-match case. Combined with the error catalog and prerequisite, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although the schema covers 100% of parameters, the description supplies semantics the schema cannot express: section requires file, on_or_after requires file, entries come back in document order, and omitting section reads every H2 section with attribution preserved. Four concrete call examples map parameter combinations to results.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence gives a specific verb and resource ("Read semantic memory from About Me/ files") and immediately specifies the data model (dated bullet entries under H2 headings). The three scoping modes (file, file+section, no args) are enumerated so the agent can distinguish this from vault_read_note and vault_memory_recall 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It contains an explicit "When to use" clause covering preferences/principles/opinions, plus a prerequisite (call vault_list_memory_files first to discover names). It also routes to alternatives by condition: vault_read_note for non-memory notes, vault_memory_recall for topic search across files.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_get_outgoing_linksGet Outgoing LinksARead-onlyIdempotent
Find all notes and files a given note or canvas links to. For notes: [[wikilinks]], markdown, ![[embeds]], and wikilinks inside frontmatter properties (e.g. related:). For canvas files: file nodes, not links inside text cards. Links inside code blocks are ignored; self-links are included. Each target is listed once, however often the note links to it. A broken link's path is its target text without any #heading or |alias, with a markdown link's %-escapes decoded.
Example: vault_get_outgoing_links({ path: "Projects/vault-cortex.md" }) Example: vault_get_outgoing_links({ path: "Diagrams/architecture.canvas" })
When to use: Navigating the graph forward, auditing broken links in one note or canvas, or checking dependencies before editing. For incoming links (what links TO a note), use vault_get_backlinks.
Parameters:
path: a note or canvas created or moved in the last few seconds is not found yet.
Returns: JSON with path, outgoing_links (array of { path, title, exists, kind, bytes, daily_note_forward_ref } sorted by target path), and count. When exists is true: kind "note" is a markdown note readable via vault_read_note; kind "file" is a non-markdown file (.canvas, image, PDF) readable via vault_read_file. When exists is false the link is broken, and kind is "note" even for a path like "diagram.png". The title field is null for files and broken links. The daily_note_forward_ref flag is true on a broken link into the vault's daily notes folder — a daily note not yet created, not genuine breakage — and false otherwise, including when the daily notes folder is the vault root or blank. The bytes field is the on-disk size of a note or file (null for broken links) — not the delivery cost: vault_read_file downscales images to fit response limits, so a large image is still cheap to read.
Errors:
"path must end in …" — add the .md or .canvas extension
A path that does not exist, or one in a dot-prefixed folder, returns an empty result (count 0), not an error, just as a note with no outbound links does. If you expected links, find valid paths with vault_search, vault_list_notes, or vault_list_files.
"[SqliteError]: …" — the search index failed, not the call; the message says what to do
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Exact vault-relative path including .md or .canvas extension (e.g. "Projects/vault-cortex.md"). Case-sensitive. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds substantial context beyond annotations: code-block links are ignored, self-links are included, duplicate targets are listed once, canvas links follow file nodes only, and broken-link paths have defined normalization. It also explains empty results for nonexistent/dot-prefixed paths, daily-note forward-reference behavior, and the bytes caveat about read_file downscaling.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Despite its length, it is organized and front-loaded: purpose, link types, examples, when-to-use, parameter caveat, returns, and errors. The extra detail is justified by the absence of an output schema and by the non-obvious link and broken-path semantics.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema and complex return semantics, the description fully explains the JSON shape, field meanings, broken-link behavior, daily-note flag, error messages, and non-error empty-result cases. An agent has enough information to call it and interpret the result without guessing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents the path parameter thoroughly. The description still adds useful semantics: the examples show note and canvas usage, and it warns that a note or canvas created or moved in the last few seconds may not be found yet.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 all notes and files a given note or canvas links to." It explicitly distinguishes notes from canvas files and later routes incoming-link queries to vault_get_backlinks, so an agent can identify the tool's scope immediately.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit "When to use" cases: forward graph navigation, auditing broken links in one note/canvas, and checking dependencies before editing. It also names the alternative vault_get_backlinks for incoming links and fallback discovery tools when an unexpected empty result occurs.
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. A failed save leaves the note unchanged.
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 body 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" — remove the character the message names
"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).
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path to the note, including the ".md" extension (e.g. "Notes/Plan.md", "Tracker.md"). Use the exact letter case. | |
| anchor | Yes | Short, unique substring of the body line to insert next to (case-sensitive). | |
| content | Yes | Content to insert (one or more lines). | |
| position | Yes | "before" inserts above the anchor line, "after" below it; the anchor line itself is never changed. | |
| first_match | No | If the anchor matches more than one line, use the first match instead of erroring (default: false — ambiguity is an error). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare non-read-only, non-destructive, non-idempotent, but the description adds real behavioral context beyond them: atomicity ('A failed save leaves the note unchanged'), line-ending normalization (CRLF to LF), property-block preservation, and a full error taxonomy including concurrent-write and paths. Minor gap: no explicit statement about the ambiguity/repeat-call risk implied by idempotentHint=false.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, then examples, when-to-use, errors, syntax notes, and returns in a scannable structure. The error list is long because the tool genuinely has many failure modes, but the two worked examples restate points made elsewhere, keeping it just short of maximally tight.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Thorough for a non-idempotent mutation tool with no output schema: return value format is described precisely, every documented error has a remediation, and Obsidian Markdown pitfalls (#, [[, %%) are flagged. What's missing is only peripheral (e.g., whether the note is created if absent is covered indirectly by 'note not found').
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline of 3 applies, and the description goes beyond it with verbatim-insertion semantics ('blank lines inside it are kept, and a trailing newline adds a blank line after the inserted block') and the first_match remedy for ambiguous anchors. Not a 5 because the anchor's case-sensitivity and uniqueness expectations are carried mainly by the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('insert content as whole lines into a note's body') plus the targeting mechanism (anchor substring, before/after). It immediately distinguishes itself from sibling insert tools by naming vault_patch_note and vault_replace_span as the alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Has an explicit 'When to use' section with concrete scenarios (table rows, list items, landmark lines) and then routes away from itself: 'Prefer vault_patch_note for heading-targeted inserts' and 'Prefer vault_replace_span when replacing a block rather than inserting next to it.' Conditions for choosing each alternative are spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_list_filesList FilesARead-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, or a path that names a file — returns an empty listing, not an error.
"absolute path blocked" / "path traversal blocked" / "hidden path blocked" — the folder is absolute, escapes the vault, names the vault root itself (e.g. "."), or is hidden like ".obsidian"; use a vault-relative folder outside hidden folders, and omit folder to list the whole vault.
"ENAMETOOLONG: …", "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).
Readable types: 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. It cannot read any type outside these lists.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max entries returned (default 50). | |
| folder | No | Folder path to search recursively (e.g. "attachments" or "Projects/media"). Omit to list the whole vault. | |
| extensions | No | Only include these extensions, case-insensitive, leading dot optional (e.g. [".png", "jpg"]). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, and the description adds real behavior beyond them: exact-case folder matching on case-sensitive filesystems, permanent invisibility of dot-prefixed files and .obsidian/, empty-listing-not-error semantics, the three distinct error classes with remediation, and truncation via `truncated`. That is disclosure of what can silently return fewer results than expected — precisely the risk an agent needs to know about for a listing tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded one-liner followed by labeled sections (Example / When to use / Behavior / Errors / Returns / Readable types), so an agent can skim to what it needs. It runs long — the three worked examples and the multi-line error table are more than strictly necessary — but nearly every line carries operational information rather than filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, and the description compensates by fully spelling out the return shape ({ path, extension, bytes }, extension_counts, total, truncated) and the sort order. Combined with error handling and the readable-type list that gates the natural next call to vault_read_file, nothing an agent needs to invoke this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description still adds value by stating the case-sensitivity rule for `folder` ("use the folder's exact letter case") and illustrating real invocations with `extensions` and `limit`. It does not re-explain `limit` truncation semantics in the parameter discussion, but the Returns section covers it, so the gap is minor.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource and an explicit scope qualifier: "List non-markdown files in the vault or a folder — images, canvases, PDFs, data files", plus what is returned (byte sizes, extension counts). The markdown/non-markdown boundary is exactly the line that differentiates it from the note-oriented siblings, so an agent can route correctly from the first sentence alone.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
A dedicated "When to use" clause names the downstream step (read with vault_read_file) and explicitly excludes three siblings with the reason: vault_list_notes and vault_search_by_folder are markdown-only, vault_search indexes only canvas/PDF/text. It even routes the link-following case to vault_get_outgoing_links, so both when-to-use and when-not-to-use are covered.
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 FilesARead-onlyIdempotent
Discovery tool — lists About Me/ memory files with each file's heading outline, per-section entry counts, and leading callout. Does NOT return actual entries.
Example: vault_list_memory_files()
When to use: Discovering what memory files and sections exist — and what each file is for — BEFORE calling vault_get_memory, vault_update_memory, or vault_delete_memory, and each file's entry_policy to check before pruning entries. To find entries by topic instead, use vault_memory_recall.
Behavior: Lists every .md file directly inside About Me/, sorted by file name. Subfolders are not read, and hidden (dot-prefixed) files are skipped.
Errors:
An empty or nonexistent memory folder returns an empty array, not an error.
"cannot list memory folder …" — a file stands at the memory folder's path, or its permissions block the server; ask the vault's owner to fix it
"cannot read memory file …" — one listed file can't be read, which fails the whole listing; retry once, then ask the vault's owner to check that file
Returns: JSON array of file outlines, each { file, title, bytes, entry_policy, leading_callout, headings } — file is the name the other memory tools take as file (no .md); title is the title property, or file when there is none; bytes is the on-disk file size; headings lists H1 and H2 headings in order, each { level, text }, with entry_count on H2s; leading_callout is the top-of-file callout ({ type, title, body }; by convention a "Scope of this file" block describing what belongs in the file), or null; entry_policy is "append-only" (the default: by convention, entries are never edited or deleted) or "living" (a current-state file whose expired entries may be pruned; declared via entry-policy frontmatter). The server does not enforce either policy.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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 substantial context beyond them: sorted by file name, subfolders not read, dotfiles skipped, empty folder returns [] rather than erroring, and the two failure strings with remediation steps. It also explains the entry_policy semantics and that the server does not enforce them — non-obvious behavior an agent needs before pruning.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded and sectioned (When to use / Behavior / Errors / Returns), so an agent can scan it quickly. Slightly over-long: the opening summary is restated almost verbatim in the Returns section, and the `Example: vault_list_memory_files()` line adds nothing for a zero-argument call.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 full burden of documenting the return shape, and it does so field by field ({ file, title, bytes, entry_policy, leading_callout, headings }) including the nested heading shape and entry_count placement. Error conditions and the empty-folder edge case are both covered, so nothing an agent needs to call and interpret this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so the baseline is 4; the description correctly spends its budget elsewhere rather than inventing parameter detail. It does clarify the identity semantics of the returned `file` value (name without .md, the same value other memory tools accept), which is useful downstream context even though this tool takes no input.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource ("lists About Me/ memory files with each file's heading outline, per-section entry counts, and leading callout") and immediately negates the most likely misread: "Does NOT return actual entries." This clearly separates it from vault_list_files (generic file listing), vault_get_memory (returns entries), and vault_memory_recall (topic search).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly stages usage: discover files/sections BEFORE vault_get_memory, vault_update_memory, or vault_delete_memory, and names the routing alternative — vault_memory_recall for finding entries by topic. Exclusions and prerequisites are both stated, 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.
vault_list_notesList NotesARead-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; 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 every matching vault-relative path, with no cap (e.g. ["Notes/idea.md", "Projects/plan.md"]).
| Name | Required | Description | Default |
|---|---|---|---|
| glob | No | Glob pattern for note paths (e.g. "**/*session-log*.md"). | |
| folder | No | Vault-relative folder to list (e.g. "About Me", "Projects"). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive and closed-world, so the safety profile is covered. The description still adds real behavioral detail beyond them: sorted output order, hidden paths never listed, symlinks included, no result cap, and empty array rather than error on misses. However, the annotation baseline is already strong, and several of these disclosures overlap with what idempotent/readOnly imply, so this lands at a solid 3 rather than higher.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then examples, then when-to-use, then parameters, then behavior and errors — a clear hierarchy. It runs long for a two-parameter read tool, and the filesystem error section (ENAMETOOLONG, EACCES) is heavier than this tool's decision surface requires, but nearly every sentence carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 compensates by specifying the return shape (JSON array of vault-relative paths), the absence of a result cap, sort order, and the empty-array-on-no-match contract. An agent can call this correctly and interpret the result without further information.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, yet the description adds semantics the schema does not carry: folder without a glob recurses into subfolders but does not prefix-match siblings ("Projects" covers "Projects/Archive" but not "ProjectsOld/"), folder matching is case-sensitive, and glob resolves against the folder-relative path with * single-level and ** arbitrary depth. This is genuinely additive over the schema's one-line parameter descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("List .md file paths in the vault") and immediately bounds scope with "Returns paths only — not content or metadata." This distinguishes it from vault_search_by_folder and vault_search, which handle metadata and content respectively.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
"When to use" names the browsing-by-filename case, then explicitly routes to alternatives with conditions: vault_search_by_folder for metadata, vault_search for content discovery, vault_read_note to read a result. Selection is fully determined without inference.
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 KeysARead-onlyIdempotent
Discover all property keys in the vault with note counts and sample values, without reading individual notes.
Example: vault_list_property_keys() returns [{ key: "tags", count: 342, sample_values: ["session-log", "project"] }, ...] Example: vault_list_property_keys({ folder: "Projects" }) — the keys used under Projects/ only
When to use: Discovering what properties exist before searching by property. Good first step for vault orientation alongside vault_list_tags. Prefer vault_list_property_values when you need the full list of values for a specific key. Prefer vault_search_by_property to find notes matching a specific key-value pair.
Parameters:
folder names a whole folder 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.
| Name | Required | Description | Default |
|---|---|---|---|
| folder | No | Restrict to a folder (e.g. "Projects") |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, yet the description goes well beyond them: it explains that only frontmatter properties count and inline Dataview fields are excluded, defines what count includes (null-valued notes), explains how sample_values are computed (3 most frequent displayed strings, array elements counted separately, booleans as '1'/'0'), and documents empty-result and SqliteError behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded and cleanly sectioned into Examples / When to use / Parameters / Behavior / Errors / Returns, so it scans well. It is on the long side, with two examples and a Returns line that partly restates the earlier example, but nearly every line carries load-bearing information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter read tool with no output schema, it supplies everything needed: output shape and sort order, scoping rules, exclusion of inline fields, counting semantics, and both silent (empty array) and error ('[SqliteError]: …') failure modes with the advice that the index, not the call, failed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 genuinely non-obvious semantics the schema does not: folder includes subfolders ('Projects' covers 'Projects/Archive' but not 'ProjectsOld/'), matching ignores ASCII case, and omitting the parameter scans the entire vault.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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') with the key differentiator from note-reading siblings spelled out: 'without reading individual notes.' It also shows the exact return shape and scoping example, so an agent immediately knows this is a metadata-level inventory call rather than a content read.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'When to use' clause, plus a direct pairing with vault_list_tags for orientation, and two named alternatives with the exact conditions that select them (vault_list_property_values for full value lists, vault_search_by_property for key-value matching). 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_list_property_valuesList Property ValuesARead-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 get the most-used values first; nothing in the response signals truncation.
Behavior:
Array elements (tags: ["a", "b"]) are 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").
| Name | Required | Description | Default |
|---|---|---|---|
| key | Yes | Property key name (e.g. "status", "type", "tags"). | |
| limit | No | Max values to return (default 50). | |
| folder | No | Restrict to a folder (e.g. "Projects") |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish that this is a read-only, idempotent, non-destructive operation, and the description adds substantial behavioral detail. It explains array element counting, grouping of equal displayed strings, checkbox coercion, null skipping, limit ordering, lack of truncation signaling, and error behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well structured with examples, usage guidance, parameter notes, behavior, errors, and returns. The extra length is justified by subtle counting, grouping, and sorting semantics that an agent could easily get wrong.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only listing tool with no output schema, the description covers everything needed: return shape, sort order, truncation behavior, empty results, and index-error handling. There are no significant omissions for correct invocation or interpretation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 100%, the description adds meaningful semantics beyond the schema. It specifies that key matching is case-sensitive and exact, that folder includes subfolders but excludes similarly prefixed names, and that limit is applied after count-descending sorting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: listing distinct values for a property key, including occurrence counts. It clearly distinguishes this from sibling tools by naming vault_search_by_property and vault_list_property_keys as related operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says when to use the tool: enumerating possible values before calling vault_search_by_property. It also gives the prerequisite action to call vault_list_property_keys first to discover valid key names.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_list_tagsList TagsARead-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. To count tags in one folder, or only the most-used ones, use vault_list_property_values({ key: "tags" }) with folder or limit.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive), so this is bonus context: frontmatter-only counting (inline #tags excluded), case-insensitive tag unification with most-common spelling shown, no hierarchical splitting, count = unique notes. It also discloses error behavior (empty array is not an error; SqliteError means index failure) — exactly the kind of operational detail annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then example, then usage, then errors — a clean progression with no filler. Minor deductions: the 'Returns' line partly restates the example, and the SqliteError bullet's guidance ('the message says what to do') is vague enough to add little.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-param read tool with no output schema, the description covers everything an agent needs: aggregation rules, ordering, casing behavior, output shape, edge case, and a sibling alternative for the folder/limit case.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so there is no input semantics to explain and the baseline is 4. The description instead invests in output semantics ('tag omits the # prefix'), which is useful given there is no output schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 with note counts') plus exact ordering semantics, and immediately distinguishes itself from tag-searching and property-value siblings. An agent knows precisely what this returns 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'When to use' section frames it as a discovery step, then names the alternative route: 'Prefer vault_search_by_tag once you know which tag', and points to vault_list_property_values for folder-scoped or limited counts. Both the when and the when-not are covered with named siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_list_tasksList TasksARead-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, ⏫ priority, 🔁 recurrence, ...) and Dataview inline fields ([due:: 2026-07-04], [priority:: high], ...). Task lines inside fenced code blocks and %% %% comment blocks are not indexed. Checkboxes typed NON_TASK in the Tasks plugin's status settings are also excluded.
Example: vault_list_tasks({ due: { before: "2026-07-04" } }) — overdue triage; the default status (not_done) and sort (due ascending) make this the "what's overdue?" call Example: vault_list_tasks({ path: "Code Projects/vault-cortex/TASKS.md", heading: ["Active", "Up Next", "Waiting On"], sort_by: "position" }) — actionable Kanban lanes in board order Example: vault_list_tasks({ folder: "Code Projects/vault-cortex" }) — all open tasks across a project tree (TASKS.md + task-notes/ subdirectories) Example: vault_list_tasks({ status: "done", done: { after: "2026-06-26" } }) — what got completed this week
When to use: Reading task status and order on one board (path + sort_by: "position"; add status: "all" to include done and cancelled cards). Also any vault-wide task triage question — "what's overdue?", "what's open per project?", "what did I finish this week?" — in one call instead of per-board reads. Prefer vault_read_note (heading mode) only when you need a lane's verbatim Markdown or a task's state right after a write. Prefer vault_search for full-text queries over note content.
Behavior: A file change takes a few seconds to appear here, 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.
sort_by: a task with none of a date sort's dates comes last; "priority" ascending puts highest first, and tasks with no priority sort between medium and low.
Errors:
"invalid due.before date: …" (any date filter's bound) — the bound isn't in YYYY-MM-DD form; use that form
"… is not a calendar date" — a date filter's bound has the YYYY-MM-DD form but names a day that doesn't exist, such as 2026-02-30; pass a real date
"path must end in …" — add the .md extension
"tag must not be empty …" — tag is "#" alone; pass the tag name
No matches returns { total: 0, tasks: [] }, not an error
"[SqliteError]: …" / "[IndexIntegrityError]: …" — the search index failed, not the call; the message says what to do
Returns: JSON { total, tasks }. Every task carries path, line, status, status_char (the checkbox character, such as "x" or "/"), description, folder (the task note's parent folder), depth (0 for top-level, 1+ for sub-tasks), is_kanban_task (true when the task's note is a Kanban board, with kanban-plugin in its properties), depends_on, and tags (the arrays are [] when empty). Every other field appears only when the task has it: heading (nearest heading above the task), created/scheduled/start/due/done/cancelled dates, priority, recurrence, on_completion, task_id, block_id, parent_block_id (sub-tasks whose parent carries a ^block-id), done_lanes (Kanban boards only: the headings of the lanes whose first line is Complete), and subtask_progress — { done, total } over the task's DIRECT checklist children; done counts status "done" only (a cancelled child counts toward total, 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.
| Name | Required | Description | Default |
|---|---|---|---|
| due | No | Due date (📅 / [due:: ]) bounds | |
| tag | No | Tag on the task line, "#" optional; matches nested tags, any letter case | |
| done | No | Done date (✅ / [completion:: ]) bounds | |
| path | No | Restrict to one note (vault-relative path ending ".md", case-sensitive) | |
| limit | No | Max results (default 50); total always reports the full match count | |
| start | No | Start date (🛫 / [start:: ]) bounds | |
| folder | No | Restrict to a folder (e.g. "Code Projects/vault-cortex") | |
| status | No | Status filter, OR-combined (default "not_done" = todo + in_progress). | not_done |
| created | No | Created date (➕ / [created:: ]) bounds | |
| heading | No | Exact heading text or array of headings, OR-combined, case-sensitive (e.g. "Active" or ["Active", "Up Next"]) | |
| sort_by | No | 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). "position" sorts by file path then line number. | due |
| priority | No | Priority levels, OR-combined; "none" selects tasks with no priority signifier | |
| cancelled | No | Cancelled date (❌ / [cancelled:: ]) bounds | |
| scheduled | No | Scheduled date (⏳ / [scheduled:: ]) bounds | |
| sort_direction | No | Sort direction. Default per field: "asc" for due/scheduled/priority/position, "desc" for start/created/done/note_mtime. When a date sort cascades (see sort_by), each field uses its own default direction; an explicit value overrides all fields uniformly. | |
| top_level_only | No | When true, only top-level tasks (depth 0) are returned — excludes indented sub-tasks and checklist items. Default false. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, and the description adds substantial context beyond them: indexing exclusions (fenced code blocks, %% %% comments, NON_TASK statuses), the staleness caveat that a just-written task may show old state, and a per-message error catalog with recovery guidance. It also states that no matches returns { total: 0, tasks: [] } rather than an error, which prevents false failure handling.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then examples, usage, behavior, parameters, errors, returns — a scannable structure where each block has a distinct job. It is long and somewhat repetitive (the examples restate points made in the prose, and the Returns inventory is dense), but almost every line carries information an agent would otherwise have to discover by trial.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 16-parameter, nested-object, no-output-schema tool, the description supplies what the structured fields cannot: return shape with per-field presence rules, subtask_progress semantics and its independence from query filters, indexing exclusions, and a named error catalog. Nothing an agent needs to call it correctly or interpret the response appears to be missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3; the description still earns credit by adding semantics the schema omits — status array expansion (["not_done","done"] matches todo+in_progress+done), the rule that date filters only match tasks that actually have that date, folder boundary and case-insensitivity behavior, and sort tie-breaking (undated tasks last, no-priority tasks between medium and low). It leaves limit, top_level_only, heading, tag, and priority purely to the schema, so it is additive rather than complete.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource+scope ("List checkbox tasks across the whole vault") and immediately names the data model it indexes (Tasks-plugin emoji signifiers and Dataview inline fields). It explicitly distinguishes itself from vault_read_note and vault_search, so an agent can pick the right tool 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Contains a dedicated "When to use" block covering board reading with sort_by: "position" and vault-wide triage, plus two explicit "Prefer X only when…" alternatives (vault_read_note heading mode for verbatim Markdown, vault_search for full-text). Four worked examples map concrete intent (overdue triage, Kanban lanes, project tree, completed this week) onto parameter shapes.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_memory_recallMemory RecallARead-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. Content-word queries ("testing philosophy", "sustainable pacing") rank best. A meta-framed query ("opinions on testing") that scores below the relevance threshold degrades to any-term keyword matching instead of returning nothing.
Example: vault_memory_recall({ query: "working hours and pacing" }) Example: vault_memory_recall({ query: "testing philosophy", file: "Opinions" })
When to use: Answering "what does my memory say about X?" or "how has my view on Y evolved?" — topic-based recall across memory files. Prefer vault_get_memory to read a known file or section verbatim; prefer vault_search for notes outside the memory layer.
Behavior: A memory file change takes a few seconds to appear here, so an entry written moments ago may not be recalled yet.
Errors:
No matching entries returns entries: [] and total: 0, not an error
An unknown file returns empty results — call vault_list_memory_files to discover valid names
"[SqliteError]: …" — the 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. search_mode is "hybrid" when semantic matching contributed and "fts" when keyword matching alone did, the any-term fallback included; reranked is true when the reranker ran.
| Name | Required | Description | Default |
|---|---|---|---|
| file | No | 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. | |
| limit | No | Cap on returned entries (default 50). | |
| query | Yes | Topic to recall, in natural language. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description adds substantially more: index lag of a few seconds, recall-over-precision tuning with marginal entries expected, threshold degradation to any-term keyword matching, and a documented error taxonomy where empty results are not errors. It also explains search_mode and reranked flags, which an agent needs to interpret the response.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core statement and cleanly sectioned by Examples / When to use / Behavior / Errors / Returns, so an agent can scan to the relevant part. It is long, however, and the return shape is introduced twice — 'Returns the relevant dated entries, up to limit, sorted oldest-first' up top and again in the Returns block — so some trimming is available.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, and the description fully compensates: it enumerates the JSON keys (entries, total, truncated, search_mode, reranked), the shape of each entry, and how file/section feed into vault_get_memory or vault_delete_memory. For a 3-parameter read tool with annotations already carrying safety, nothing an agent needs to call or interpret it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 goes beyond it: it advises content-word query phrasing ('testing philosophy', 'sustainable pacing') as ranking best and explains the meta-framed query fallback, and it clarifies that the file filter is applied before limit, which materially changes what limit means. Slight gap: limit's interaction with the 9007199254740991 maximum is not discussed, and query construction guidance stays at the example level.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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'), plus the retrieval mechanism (entry-granular hybrid keyword + semantic) and scope (ALL About Me/ files, ALL time). An agent can immediately distinguish this from vault_get_memory (verbatim read of a known file) and vault_search (notes outside the memory layer) 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'When to use' block names the two triggering question shapes ('what does my memory say about X?' and 'how has my view on Y evolved?') and routes to both alternatives with the conditions that select them: vault_get_memory for a known file/section verbatim, vault_search for non-memory notes. 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_move_noteMove NoteADestructive
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:) — and Canvas cards that embed the note are updated to the new path; the moved note's own relative links are fixed so they still resolve from the new folder, including relative links to attachments (e.g. ![[../assets/photo.png]],
). A link is only rewritten when leaving it unchanged would break it, so a short [[Note]] that stays unambiguous after a folder move is left alone.
Example: vault_move_note({ old_path: "Inbox/Draft.md", new_path: "Inbox/Spec.md" }) — pure rename. Example: vault_move_note({ old_path: "Inbox/spec.md", new_path: "Inbox/Spec.md" }) — case-only rename; works even where the filesystem treats both spellings as one file. Example: vault_move_note({ old_path: "Inbox/Spec.md", new_path: "Projects/Spec.md" }) — move to another folder. Example: vault_move_note({ old_path: "Inbox/Spec.md", new_path: "Projects/Spec.md", prune_empty_folders: true }) — also remove "Inbox" if the move empties it.
When to use: Renaming a note or relocating it to a different folder while keeping the link graph intact. Prefer this over vault_write_note + vault_delete_note, which would orphan every backlink. To only change a note's body or properties, use vault_patch_note or vault_update_properties. Protected paths 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: …" — this tool never overwrites; pick a free new_path or delete the existing note first.
"note not found: …" — old_path does not exist; verify path with vault_list_notes.
"source and destination are the same path" — nothing to move; pass a different new_path.
"cannot move protected path …" / "cannot move into protected path …" — old_path or new_path sits under a protected folder; pick a new_path outside it, or leave a protected note where it is.
"cannot read daily notes config from .obsidian/daily-notes.json" — ask the vault's owner to repair it, or the server operator to set DAILY_NOTES_FOLDER or PROTECTED_PATHS, then retry.
"path must end in …" — add the .md extension.
"absolute path blocked" / "path traversal blocked" / "hidden path blocked" — use vault-relative paths with no hidden (dot-prefixed) file or folder in them.
"concurrent write in progress" — a write is in flight on the note, the destination, or one of its backlink sources; retry the move.
"backlink set did not stabilize" — notes linking to the note kept changing during the move; nothing was written; retry the move.
"move aborted: …" — nothing was written. "could not read …" names the note, or a note linking to it, that could not be read; fix that note, then retry. "could not rewrite …" says why a note could not be saved with its links rewritten, and how to fix it. "could not write to …", or "could not rename to …" for a case-only rename, means new_path could not be created (new parent folders it needed may remain); retry once, then ask the vault's owner to fix the folder or disk.
"move incomplete: …" — the move failed partway; no data is lost, and the message names what failed, the state it left, and the step that finishes the move.
"ENAMETOOLONG: …", "EACCES: …" or another filesystem error code — the message says what to do; nothing was written.
"[SqliteError]: …" — the search index failed and nothing was written; the message says what to do.
Obsidian syntax: Link rewrites preserve each link's existing form — embed marker (!), heading anchor (#…), and alias (|…) are kept; a markdown link keeps its original extension and link text.
Returns: JSON with moved_to (the new path), links_updated (count of link occurrences rewritten, including the moved note's own relative links), updated_notes (sorted paths of the other notes and Canvas files that were edited; the moved note is implied by moved_to), and pruned_empty_folders (count of empty parent folders removed).
| Name | Required | Description | Default |
|---|---|---|---|
| new_path | Yes | Destination vault-relative path (e.g. "Projects/Spec.md"). Must end in .md; parent folders are created as needed. Use the exact letter case of existing folders; a different case can create a second folder. | |
| old_path | Yes | Current vault-relative path of the note to move (e.g. "Inbox/Draft.md"). Must end in .md. Use the exact letter case. | |
| prune_empty_folders | No | When true, also remove parent folders of old_path that the move leaves empty. Default false. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true and non-idempotence, but the description adds substantial context the annotations cannot: exactly which link forms are rewritten, when links are left alone, preservation of embed/heading/alias syntax, no-overwrite behavior, case-only rename support, partial-failure states, and best-effort pruning. This is far beyond the annotation baseline.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The core purpose and alternatives are front-loaded, and examples are clearly separated. The exhaustive error catalog is valuable for recovery but makes the description long; every item earns its place, yet the overall density is heavy enough that it is not maximally concise.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex, destructive move tool with three parameters and no output schema, the description covers return values, all relevant error states, protected-path constraints, link-rewrite behavior, and partial-failure semantics. Nothing an agent needs in order to invoke or recover from this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the parameters are already documented. The description still adds useful semantics beyond the schema: prune_empty_folders keeps any folder containing even a hidden file, in-place moves and moves into subfolders prune nothing, pruning is best-effort, and path case/extension rules are reinforced with examples. The extra value is real but not indispensable given the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource — moving or renaming a note while rewriting all links across the vault — and explicitly names the sibling alternatives it replaces (vault_write_note + vault_delete_note, vault_patch_note, vault_update_properties). An agent can distinguish this from every other note-mutating sibling 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides explicit when-to-use guidance, names the alternatives for body/property edits, explains why not to use delete+write, and documents protected paths that cannot be moved. The examples and error guidance further clarify selection conditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_patch_notePatch NoteADestructive
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: a no-heading prepend whose content starts with a heading nests any content above the first heading inside the new section.
replace: replace section body (requires heading); to empty it, use vault_delete_span on its lines
insert_before: insert content above the heading line (requires heading)
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.
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" — the message lists the available headings
"ambiguous heading" — multiple headings match; use heading_level to disambiguate; if they share a level, rename one or use vault_replace_in_note to target by text content
"operation … requires a heading target" — pass heading, or use append or prepend to edit the file body
"heading cannot be empty" — heading is whitespace only; pass the heading's text
"content begins with the heading … which would duplicate it" — content's first line repeats the target heading; omit it
"section … has N child headings …" — the target section contains child headings that replace would destroy; pass include_children: true to confirm, or target the child heading directly
"absolute path blocked" / "path traversal blocked" / "hidden path blocked" — 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" — remove the character the message names
"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. 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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path to the note, including the ".md" extension (e.g. "TASKS.md", "Projects/plan.md"). Use the exact letter case. | |
| content | Yes | Markdown content to insert, or the section's new body for replace. | |
| heading | No | Target heading text (case-sensitive exact match). Omit for a file-level append or prepend. | |
| operation | Yes | The edit to apply. | |
| heading_level | No | Heading level (1-6) for disambiguation when multiple headings share the same text | |
| include_children | No | When true, allows replace to overwrite a section that contains child headings. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true and idempotentHint=false, but the description goes far beyond them: it discloses LF normalization of CRLF, that content is written verbatim with no separator, section-boundary rules including child-heading inheritance, and a warning that replace destroys child headings unless include_children is set. It also enumerates concurrency behavior ("concurrent write in progress"), path-safety blocks, and the exact return confirmation format.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Placeholder.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive six-parameter mutation tool with no output schema, the description covers purpose, routing, operation semantics, boundary edge cases, a full error taxonomy with remedies, and the return message shape — including the special nested-prepend confirmation. An agent has everything needed to call it correctly and to recover from failures.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 substantial semantics the schema does not carry: per-operation behavior for append/prepend/replace/insert_before, when heading may be omitted, heading_level as the disambiguator for duplicate headings, and include_children as a destructive-write confirmation gate. The table-row and callout guidance further constrains how content must be formed.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Surgical edits to a markdown note") and immediately enumerates the four supported operations, so the agent knows exactly what capability this exposes. The "When to use" block explicitly contrasts it with vault_write_note, vault_replace_in_note, vault_insert_at_anchor, vault_replace_span, vault_delete_span, and vault_update_properties, making it distinguishable from every overlapping sibling.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
"Modifying part of an existing note without overwriting the entire body" gives the positive selection criterion, and each of the ten "Prefer X for Y" lines names the alternative and the condition that routes to it. Exclusions are explicit (creating notes, full rewrites, in-place text fixes, property edits, task operations), 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.
vault_read_fileRead FileARead-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
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.
What each file type returns:
Images (.png/.jpg/.jpeg/.gif/.webp): the image as an image block, downscaled and recompressed when it exceeds the image output budget (49152 bytes) or has a longer side over 1568 pixels, and delivered untouched otherwise. Animated GIFs are reduced to their first frame when recompressed.
Canvas (.canvas): a readable markdown outline — groups, node content in reading order, and a connections list with edge labels.
PDFs (.pdf): structured text with document metadata — title, page count, heading hierarchy, code blocks and inline code, page separators, and a links footer.
Text formats (.svg/.json/.txt/.csv/.xml/.log/.yaml/.yml/.base): the file content verbatim as text.
raw: true switches a canvas or PDF to its other form:
Canvas: the exact JSON source (geometry, ids, colors) instead of the outline.
PDF: each page rendered as an image block instead of extracted text, showing layout, diagrams, tables, and formatting. 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 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 (a fixed 100 KiB) applies to each window, so one very long line can still overflow it; paging never gets around the file-size cap. Paged windows come back with \n line endings and no trailing newline; an unpaged read of a text format returns the file byte-exact.
Errors:
"not a file: … is a markdown note" — the path ends in .md; read notes with vault_read_note
"file not found" — no file exists at that path (a folder is not a file); discover valid paths via vault_list_files
"absolute path blocked" / "path traversal blocked" / "hidden path blocked" — use a vault-relative path with no hidden (dot-prefixed) file or folder in it
"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
"not valid UTF-8" — the file's bytes aren't UTF-8 text; ask for a UTF-8 copy
"invalid .canvas JSON" — the canvas file is empty or not valid JSON; set raw: true to read its source as text
"PDF has no extractable text" — the PDF contains no text (scanned or image-only); the error states the page count. Set raw: true to render pages as images instead
"PDF is password-protected" — this tool cannot supply a password; read an unprotected copy instead
"PDF is damaged or not a PDF" / "could not decode image" — the PDF does not parse, or the image is empty, damaged, not an image despite its extension, or over about 268 million pixels; any other PDF or image error comes back in the reading library's own words. Replace or re-export the file
"PDF page rendering failed" — read it without raw: true to get its text instead; if that read also finds no text, this tool can't read the PDF
"image cannot be fitted into …" — the image is over the image output budget even after shrinking; ask the server's owner to raise MAX_IMAGE_OUTPUT_BYTES
"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; 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.
| Name | Required | Description | Default |
|---|---|---|---|
| raw | No | Return a canvas's or PDF's alternative form. Changes nothing for text formats, which already return their source. | |
| path | Yes | 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. | |
| limit | No | Maximum lines returned (default: all remaining). | |
| start_line | No | First line to return, 1-based (default 1). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive), yet the description goes far beyond them: it documents the 49152-byte image budget and 1568-pixel downscale threshold, the 100 KiB text cap, the 5-page limit on PDF raw rendering, the 50 MiB file cap, the first-frame reduction for animated GIFs, the byte-exact vs. paged newline difference, and a full error catalogue with recovery actions. This is the load-bearing content 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well front-loaded and sectioned (examples, when-to-use, return shapes per type, raw semantics, paging, errors, returns), so an agent can scan to the needed part. However, the roughly two-dozen-error catalogue is disproportionately long for a read tool and includes entries whose recovery is obvious (EACCES, ENAMETOOLONG); it could be compressed to the errors that change agent behavior.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 4-param read tool with no output schema and a highly variable return type, the description supplies exactly what is missing: what each content block looks like per file type, how paging manifests in the output, and how each failure mode should be retried. Nothing an agent needs to invoke it correctly or recover from failure is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but the description still adds real meaning: raw is explained as a canvas-JSON vs. outline switch and a PDF-images vs. extracted-text switch (and that it is a no-op for text formats), and start_line/limit are described as a line window with a concrete continuation metadata line format, plus the tip of requesting start_line: 1 with limit: 1 to learn total line count. It also states which combinations error out (paging on images, raw on images).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Read a non-markdown vault file") and explicitly positions itself as the read-side companion to vault_read_note, defining the boundary at .md. The per-type examples make the scope concrete and distinguishable from every sibling in one sentence.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
A dedicated "When to use" block names the alternatives and the conditions that select them: vault_read_note for .md, vault_get_outgoing_links for discovering linked files, vault_list_files for browsing, and an explicit note that vault_search indexes some but not all of these types. Exclusions are stated, not inferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_read_noteRead NoteARead-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.
Example: vault_read_note({ path: "Projects/vault-cortex.md" }) Example: vault_read_note({ path: "TASKS.md", outline: true }) Example: vault_read_note({ path: "TASKS.md", heading: "Active" }) Example: vault_read_note({ path: "TASKS.md", heading: "Done", heading_level: 2 }) Example: vault_read_note({ path: "TASKS.md", heading: "Done", start_line: 1, limit: 20 })
When to use: You know the exact path and need a specific note's content. For a large note (a long Kanban board or doc), use outline: true to see its headings and any text sitting above them, then heading: "..." to read just the one section you need. For a note or section too long for one response (this tool sets no cap), 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" — the message lists the available headings
"ambiguous heading" — multiple headings match; use heading_level to disambiguate, or read the full note (omit heading) when headings share the same level
"outline, heading, and properties_only are mutually exclusive — set at most one"
"heading_level requires a heading" — pass heading with it
"heading cannot be empty" — heading is whitespace only; pass the heading's text
"line paging is not available in outline mode" / "… properties_only mode" — start_line and limit page only a full read or a heading section
"start line past the end" — the message states the line count; resend a start_line within it
'path must end in ".md"' — add the extension to a note's path; read other files (images, .canvas, data files) with vault_read_file
"absolute path blocked" / "path traversal blocked" / "hidden path blocked" — 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 size and modified is its modification time (ISO 8601); each heading's bytes is the size 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 or the full note, and edit via vault_replace_in_note.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path to the note, including the ".md" extension (e.g. "About Me/Principles.md"). Use the exact letter case. | |
| limit | No | Maximum lines returned (default: all remaining). | |
| heading | No | Return only this section. Case-sensitive exact match. | |
| outline | No | If true, returns { bytes, modified, leading_callout?, leading_content?, headings } as JSON instead of body content. 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_line | No | First line to return, 1-based (default 1). | |
| heading_level | No | Heading level (1-6) for disambiguation when multiple headings share the same text | |
| properties_only | No | If true, returns parsed properties as JSON instead of full note content |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well past the readOnly/idempotent annotations: it discloses that the tool sets no response cap, that unpaged full reads return exact bytes while heading/paged reads normalize to LF, that sections extend to the next same-or-higher heading, and how window metadata is prepended. It also documents a full error catalog with remediation for each, which is exactly the burden a description should carry when there is no output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well front-loaded: purpose, then examples, then when-to-use, errors, returns, outline semantics — easy to scan. However, it is very long, with five examples and a ~12-entry error catalog; a few entries (e.g. generic filesystem error codes, ENAMETOOLONG) are marginal and push it past what most calls need.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 specifies return shapes for all four modes (raw markdown, properties JSON, outline object, section markdown) plus the window-metadata preamble and byte/line-ending differences. All seven parameters are covered via schema plus description, so 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, so the baseline is 3; the description earns more by explaining relationships the schema cannot express — outline/heading/properties_only are mutually exclusive, heading_level only disambiguates same-text headings, and start_line/limit apply only to full or heading reads. The line-count trick (start_line: 1, limit: 1) adds genuine operational meaning beyond the field descriptions, though some detail is duplicated rather than new.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource ('Read a markdown note by its vault-relative path') and immediately enumerates the four content modes. It explicitly distinguishes itself from vault_search (unknown path), vault_get_memory (About Me files), and vault_list_tasks (task status) without the agent needing to open sibling schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Contains an explicit 'When to use' section with the positive condition (you know the exact path) plus named alternatives for each divergent case: vault_search, vault_list_tasks, vault_get_memory, vault_patch_note, vault_get_backlinks, vault_get_outgoing_links. It also prescribes an escalation path for large notes (outline → heading → start_line/limit) rather than leaving it to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_recent_notesRecent NotesARead-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.
Behavior: A file change takes a few seconds to appear here, so a note written moments ago may not be listed yet.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max results (default 20, no upper cap) | |
| sort_by | No | Timestamp to sort by (default "modified") | modified |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and non-destructive, so the safety profile is covered by structured data. The description still adds real value beyond that: index lag of a few seconds, the fact that "modified" tracks sync touches and property writes, null created values sorting last, and how [SqliteError]/[IndexIntegrityError] messages should be read as index failures rather than call failures.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the key distinction, which is good, but the block is long for a two-parameter tool and repeats the same sort_by/limit tradeoff across the Parameters section and the example lines. The two example calls largely duplicate what the schema defaults already convey.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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-value burden and does so thoroughly: it enumerates the metadata fields, calls out that created is null for missing/non-ISO frontmatter, defines bytes as on-disk size, and explains when leading_callout and additional_properties are present. Empty-vault and error cases are also covered, so nothing needed for a correct call is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both parameters are already documented, giving a baseline of 3. The description does go further by explaining the interaction between sort_by and limit (undated notes sorting last can be pushed out by a small limit), but it restates the enum semantics rather than adding syntax or format detail the schema lacks.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("List recently modified or created notes, sorted by timestamp") and immediately delimits scope with "a time-ordered window into the vault, not a date-range filter." That contrast alone separates it from vault_search with its created/modified range filters, so an agent can pick it 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
An explicit "When to use" list names the concrete situations (catching up, finding recent work, orienting after a break), and two named alternatives are offered with the condition that selects them: vault_search for content queries, vault_search_by_folder for folder browsing. 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_replace_in_noteReplace in NoteADestructive
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=""). 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: a non-empty value replaces the match exactly. For a deletion (new_text=""):
When text remains on the line, it is written exactly as asked.
When the line empties, blank lines never double: the text above and below is 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").
To remove a table row or list item, include its line break in old_text, or the empty line left behind splits the table or list.
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" — remove the character the message names
"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 ".
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path to the note, including the ".md" extension (e.g. "Projects/plan.md"). Use the exact letter case. | |
| new_text | Yes | Replacement text. Empty string ("") deletes the matched text. | |
| old_text | Yes | Exact text to find, no regex (case-sensitive). | |
| replace_all_occurrences | No | Replace all occurrences (default: false — replaces first occurrence only) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true, idempotentHint=false, and readOnlyHint=false, so the safety profile is covered structurally; the description nonetheless adds substantial value beyond them — CRLF-to-LF normalization, properties left verbatim, the blank-line collapse rules, control-character rejection, and the 'concurrent write in progress' retry path. It loses points only because several of these blocks (the deletion blank-line algebra) are dense to the point of being hard to act on in one read.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose, examples, when-to-use, parameters, errors, and return value are cleanly sectioned and front-loaded, and the three examples map to the three distinct modes (replace, delete, replace-all). It is on the long side for a four-parameter tool, and the multi-level blank-line rules are the one passage that could be tightened.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description correctly states the return string ('Replaced <N> occurrence(s) in <path>') and enumerates the error surface including path-safety and encoding failures. Nothing an agent needs in order to call this correctly is missing; the only residual gap is that the note-mutating semantics for the 'properties block' error are described but not resolvable without a follow-up read.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description does go further by advising that old_text include surrounding context for uniqueness, spelling out that new_text="" deletes, and warning to include the trailing line break when removing a table row or list item. Those are real additions beyond the schema, but the largest chunk of the parameter section is the blank-line normalization rules, which are output-shape behavior rather than parameter meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource: 'Find and replace exact text in a markdown note's body,' and immediately delimits scope by saying properties are untouched and pointing at vault_update_properties/vault_write_note for them. An agent can distinguish this from vault_replace_span and vault_patch_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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
An explicit 'When to use' sentence names concrete cases (typos, value updates, term renames, short-line deletion) and routes three alternative scenarios to specific siblings: vault_delete_span for large block deletion, vault_replace_span for anchor-based replacement, and vault_patch_note with an add-before-delete rationale for relocations. This is exactly the when/when-not/alternative structure the top score requires.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_replace_spanReplace SpanADestructive
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 start_anchor and end_anchor match the same line, only that one line is replaced. A span that includes a code block's opening fence can't end at a plain closing fence (```): the end anchor is ambiguous, and first_match: true would end the span at the opening fence; to replace such a block, use vault_replace_in_note.
content's line breaks:
A line break at its start or end merges with the note's empty lines on that side, keeping the wider run. At the note's end, its trailing line breaks count one fewer.
Content ending in a line break ("| X |\n") leaves an empty line after it, which splits a table.
Empty lines inside content and elsewhere in the note are kept as written, and a line holding only spaces or tabs counts as text, not as an empty line.
Errors:
"note not found" — verify path with vault_list_notes
"path must end in …" — add the .md extension
"start anchor not found" / "end anchor not found" — no body line holds the fragment where it is searched; 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" — remove the character the message names
"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).
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path to the note, including the ".md" extension (e.g. "Tracker.md", "Notes/Plan.md"). Use the exact letter case. | |
| content | Yes | Replacement content (one or more lines). | |
| end_anchor | No | Short substring of the block's LAST line, a body line (case-sensitive). Omit to replace just the single line containing start_anchor. | |
| first_match | No | If an anchor matches more than one line, use the first match instead of erroring (default: false — ambiguity is an error). | |
| start_anchor | Yes | Short, unique substring of the block's first line, a body line (case-sensitive). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and readOnlyHint=false, so the description goes beyond that with real behavior: properties are preserved, CRLF is normalized to LF, content line breaks merge with adjacent empty lines and can split tables, empty/whitespace-only lines are treated distinctly, and concurrent writes are rejected with retry guidance.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose, then examples, then guidance, then parameters and errors – well structured with headers. It is long, and the exhaustive error catalog is heavier than most calls need, but nearly every line carries actionable information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers purpose, alternatives, tricky parameter interactions, Obsidian Markdown escaping gotchas (#, [[, %%), the full error surface with recovery steps, and the exact return string. Nothing material is missing for a destructive 5-parameter mutation tool with no output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the schema already documents path, start_anchor, end_anchor, first_match and content. The description does add genuine edge-case meaning (end_anchor searched at or after the start line, same-line collapse, first_match ending at the opening fence), which would push it slightly above the baseline but not to a full 5 since most parameter semantics are already in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (replace), resource (a contiguous block of whole lines in a note's body) and the distinctive mechanism (anchor substrings instead of old_text). This clearly separates it from vault_replace_in_note and vault_patch_note without needing to open their schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Has an explicit 'When to use' section, plus named alternatives with the conditions that select them: vault_replace_in_note for small in-place edits and vault_delete_span for removals. It also routes the code-block-fence case to vault_replace_in_note instead.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_searchSearch NotesARead-onlyIdempotent
Hybrid search across vault notes and file content (canvas, PDF, and text files), ranked by combined keyword and semantic relevance. Semantic matching finds notes even when exact keywords differ — "career aspirations" finds notes about "goals" and "targets". Unquoted terms use implicit AND with stemming ("running" also matches "run"); wrap in double quotes for exact phrases; punctuated terms (vault-cortex, deploy/local) are matched as exact adjacent-word phrases.
Filters — all conditions AND-combine with each other and the text query; any filter but folder limits results to notes:
folder: a whole folder, subfolders included — "Projects" covers "Projects/Archive" but not "ProjectsOld/"; ignores ASCII letter case
tags: every listed frontmatter tag must match, in any letter case; "project" also matches "project/a"
properties: keys and text values match exactly, letter case included; values compare by exact type — pass a number as a number, not "4". Exception: true and false compare as 1 and 0, so true or 1 matches a checked checkbox and the number 1. A list property matches when any element equals the value.
created / modified: bounds compare whole calendar days in the server's time zone (returned created and modified values show its UTC offset), so "on" matches anywhere within the day. Notes without a parseable created property never match a created filter
Example: vault_search({ query: "how the server watches for file changes" }) Example: vault_search({ query: "kubernetes networking", filters: { tags: ["reference"] } }) Example: vault_search({ query: "meeting notes", filters: { type: "meeting", folder: "Work" } }) Example: vault_search({ query: "decision", filters: { modified: { after: "2026-06-30" } } }) — matching notes touched in July or later
When to use: The primary discovery tool for content-based queries, optionally constrained by metadata. Prefer vault_search_by_tag for tag-only queries without text. Prefer vault_search_by_folder for browsing a folder. Prefer vault_search_by_property for metadata-only queries. Prefer vault_recent_notes for time-based browsing.
Behavior: Results can lag a file change by a few seconds, so a note written moments ago may not be found yet.
Errors:
No matches returns an empty results array with total 0, not an error
Malformed query syntax never throws: the words AND, OR, NOT and NEAR in any letter case, and punctuation such as *, ^, parentheses and colons, are dropped before keyword matching; a query left with no terms is matched by meaning only, or finds nothing when only keyword matching ran (search_mode "fts")
"invalid created.on date: …" or "… is not a calendar date" (any created/modified bound) — pass a real calendar date in YYYY-MM-DD form
"tag must not be empty …" — a tags entry is only "#"; pass the tag name
"[SqliteError]: …" / "[IndexIntegrityError]: …" — the search index failed, not the call; the message says what to do
Returns: JSON with results array (path, title, snippet, score, tags, folder (a note's top-level folder; a file's full parent folder), type, kind, extension, created, modified, bytes), total (results returned, not all matches), search_mode ("hybrid" when vector embeddings contributed, "fts" when only keyword matching ran), and reranked (true when reranking refined the ordering). A higher score is more relevant. The kind field is "note" for markdown notes or "file" for non-markdown content; file results also carry extension (e.g. ".pdf"). The created field is omitted, not null, when a result has none; type is null when the result has no type property. The bytes field is the on-disk file size. With include_leading_callout, each result also carries leading_callout ({ type, title, body }) when present.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max results (default 20) | |
| query | Yes | Search query text | |
| filters | No | Optional structured filters | |
| snippet_tokens | No | Snippet length in words, not model tokens (default 30) | |
| include_leading_callout | No | If true, include each result's leading callout. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Although annotations already establish that this is a safe, read-only, idempotent operation, the description adds substantial behavioral context: hybrid ranking, query parsing rules, filter AND-combination, date-bound timezone behavior, index lag, and error handling. It also distinguishes empty results from errors and explains how malformed syntax is handled.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well structured and front-loads the core purpose, filters, examples, and alternatives. It is unusually long, with detailed error strings and return-field notes, but most of the content is useful for correct invocation; the length is justified though not maximally concise.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, 100% schema coverage, annotations, and lack of an output schema, the description is complete enough for an agent to call it correctly. It covers query syntax, filter semantics, alternatives, edge cases, error behavior, and the shape of returned results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 meaningful semantics beyond the schema. It clarifies stemming, exact-phrase quoting, punctuated-term behavior, folder case/subfolder rules, tag case/prefix matching, property type comparison including the boolean/1 exception, and calendar-day date-bound behavior.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource — hybrid search across vault notes and file content — and explains the ranking model. It explicitly distinguishes this as the primary content-based discovery tool versus tag-only, folder-browsing, property-only, and time-based sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit when-to-use guidance and names alternatives for tag-only, folder browsing, metadata-only, and recent-note queries. The examples also show how text queries combine with filters in common scenarios.
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 FolderARead-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. There is no offset, and nothing in the response signals truncation; raise limit to see more.
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 (file name without .md when missing or not text), tags, related ([] when missing), folder (top-level folder), type (null when missing or not text), created (frontmatter; null when missing or not an ISO date), 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).
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max results (default 20) | |
| folder | Yes | Folder path (e.g. "Projects", "About Me") | |
| recursive | No | Include subfolders (default: true) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (readOnly, idempotent, non-destructive, closed-world), and the description adds genuinely new behavioral context: index lag of a few seconds after file changes, hidden dot-folders never appearing, and the fact that truncation is silent with no offset. Error handling is spelled out per class (empty folder returns [], index errors are not call failures).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then examples, routing, parameters, behavior, errors, and returns in scannable blocks; the inline gloss on the second example ("the folder's own notes, 10 most recent") is well spent. It is on the long side for a three-parameter read tool and the Returns field list is dense prose that could be trimmed slightly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 value itself, documenting every field including sort order, null-vs-empty conventions (related [], type/created null), and conditional keys (leading_callout, additional_properties). Edge cases, silent truncation, and error semantics are all covered, so nothing needed 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Despite 100% schema coverage, the description materially extends the schema: folder is a whole-folder match rather than a text prefix ("Projects" does not match "ProjectsOld/"), matching is ASCII-case-insensitive, and a trailing slash is ignored. It also clarifies that limit applies after sorting and that there is no offset or truncation signal — none of which the schema states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb + resource + scope ("Browse notes in a folder with full metadata"), which distinguishes it from path-only listing and text search. The description explicitly names the sibling alternatives (vault_list_notes, vault_search, vault_get_backlinks) so an agent can route 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
"When to use: Exploring a folder's contents with full context for vault orientation" is followed by two explicit alternative-routing rules (path-only → vault_list_notes, text query → vault_search). Link exploration is also redirected to vault_get_backlinks/vault_get_outgoing_links, leaving no ambiguity about when this tool is the wrong choice.
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 PropertyARead-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) matches only as text.
value for a checkbox: pass "1" (checked) or "0" (unchecked); a checkbox matches only as text, so "1.0" and "true" do not match.
value against a list property: an 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.
Behavior: Results can lag a file change by a few seconds: a note written moments ago may not match yet, and one changed moments ago may still match its old value.
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, then by path — recently-synced notes may sort ahead of older content edits.
folder is the top-level folder ("" at the vault root); title falls back to the file name without .md when missing or not text; related is [] when missing; type is null when missing or not text; created is null when missing or not an ISO date.
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 tags.
| Name | Required | Description | Default |
|---|---|---|---|
| key | Yes | Property key name (e.g. "status", "type", "tags"). | |
| limit | No | Max results (default 20, no upper cap) | |
| value | Yes | Value to match (e.g. "active", "4", "1e-7"). | |
| folder | No | Restrict to a folder (e.g. "Projects") |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent annotations: discloses index lag ('a note written moments ago may not match yet'), that truncation is invisible ('nothing in the response signals truncation'), the exact sort order and its mtime caveat, and the error taxonomy (empty array vs SqliteError vs IndexIntegrityError). This is precisely the 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and examples, then clean labelled sections (When to use, Parameters, Behavior, Errors, Returns) that make the length scannable. It is long, but nearly every line carries non-obvious information; only the returns-field enumeration could be tightened.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description carries the return contract — it documents the array shape, sort order, fallback values for title/related/type/created, and when optional fields appear. Combined with error and lag disclosure, an agent has everything needed to call and interpret this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 adds substantial semantics the schema lacks: case-sensitive exact matching with no globbing, the numeric-coercion rules for finite YAML numbers, checkbox '1'/'0' matching as text only, list element full-equality ('blog' vs 'my-blog'), and folder including subfolders but excluding 'ProjectsOld/'.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource+scope: 'Find notes where a frontmatter property matches a value', with two concrete invocation examples. It is immediately distinguishable from vault_search and vault_search_by_tag by the explicit routing sentences that follow.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'When to use' section states the condition (metadata lookup without a text query) and names three alternatives with the conditions that select them: vault_search for text+property, vault_search_by_tag for tags, vault_list_property_keys/values for discovery. 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_tagSearch by TagARead-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.
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 (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).
| Name | Required | Description | Default |
|---|---|---|---|
| tag | Yes | Tag, "#" optional (e.g. "project", "session-log", "project/vault-cortex") | |
| exact | No | Match only the tag itself (default: false) | |
| limit | No | Max results (default 20, no upper cap) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the read-only/idempotent annotations, it discloses case-insensitivity, that inline tags in bodies are not searched, nested-tag matching rules with concrete examples, that limit is applied after sorting with no truncation signal in the response, and that results can lag file changes by seconds. Error semantics (empty array vs. real errors, trailing-slash gotcha, SqliteError/IndexIntegrityError meaning) are unusually complete.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core rule and examples, then cleanly sectioned into usage, parameters, behavior, errors, and returns. Slightly long in the Returns enumeration, but with no output schema that detail is load-bearing rather than filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 fully specifies the return shape (fields, sorting, defaults, ISO timestamps, optional leading_callout/additional_properties). Combined with lag behavior and error taxonomy, an agent has everything needed to call and interpret this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description earns above that by explaining that with exact=false a tag matches all nested descendants at any depth, that 'project' won't match 'projects' or 'my-project', and that limit is applied after sorting. It does not restate the tag format details already in the schema, which is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Find notes by frontmatter tag') and immediately narrows scope by excluding inline #tags. It is clearly distinguishable from siblings such as vault_search (text relevance), vault_search_by_property, and vault_list_tags, which it explicitly references.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides an explicit 'When to use' section ('tag-only lookups ... no text query'), names vault_list_tags as the discovery step, and routes text-based queries to vault_search. When-to-use and alternatives are both covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_update_memoryUpdate MemoryAIdempotent
Append a dated entry to a section of a memory file in About Me/. An exact duplicate (same date + text in the same section) is a no-op, so retrying a timed-out call is safe. Memory files are append-only by default: when a preference changes, append the new state (newest wins) rather than deleting the old one. A file may declare entry-policy: living in frontmatter (surfaced by vault_list_memory_files) — a current-state file where pruning expired entries with vault_delete_memory is expected maintenance.
Example: vault_update_memory({ file: "Opinions", section: "Code patterns (newest first)", entry: "Prefer immutable data structures" }) Example: vault_update_memory({ file: "Routines", section: "Daily/weekly rhythm (newest first)", entry: "Gym Mon/Wed at 6am", options: { date: "2026-10-01", position: "bottom" } }) — a backdated entry placed below the existing ones
When to use: Recording a new preference, principle, opinion, or fact about the user. Call vault_list_memory_files first and reuse existing file and section names so entries stay grouped. Prefer vault_write_note for creating non-memory notes. A missing file or section is created automatically.
Parameters:
options.date only sets the bullet's date; placement follows options.position, never the date.
Obsidian syntax: Entry text is Obsidian Flavored Markdown. Watch for: #word = tag, [[ = wikilink. Escape with # or backticks when unintentional.
Errors:
"entry must be a single line" / "section must be a single line" — the message names the fix.
"date must be a real ISO calendar date" — pass options.date as an existing calendar date in bare YYYY-MM-DD form, not a timestamp.
"entry contains a control character" / "section contains a control character" — remove the character the message names.
"memory file must not start with a dot" / "memory file must be a bare name without path separators" / "memory file must be named without .md" — each message names the part of the name to drop
"section not created: … is nearly identical to existing section …" — pass the exact existing heading (listed in the error) to append there, or choose a clearly different name for a new section.
"cannot read memory file …" — the file can't be read: check file against vault_list_memory_files and shorten an over-long name; otherwise ask the vault's owner to fix its permissions
"cannot write memory file …: that path is not a file" — a folder already has this name; choose another file name
"ENAMETOOLONG: …", "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."
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | Memory file name without .md (e.g. "Principles"). Use the exact letter case; a different case can create a second file. | |
| entry | Yes | Raw entry text — a single line, without the "- **YYYY-MM-DD**: " prefix, which the server prepends. | |
| options | No | Optional date and position overrides | |
| section | Yes | H2 section heading (e.g. "Decision heuristics (newest first)"). Matched case-insensitively, with or without the "(newest first)" suffix. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag idempotentHint=true, but the description earns its keep beyond them: it explains the exact no-op condition (same date + text in same section), the append-only default with a 'newest wins' resolution for changed preferences, the living-file exception, and automatic file/section creation. It also documents failure modes (permissions, path collisions, near-duplicate section headings, properties-block hazard) that no structured field could carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well front-loaded: purpose, two worked examples, when-to-use, then parameters and returns. The error catalogue is long and largely restates error strings the agent will see at call time, which is the only thing holding this back from 5; every other section earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 states the exact return strings, including the duplicate no-op return and the new-file placeholder scope callout. Combined with error handling, the date/position interaction, and the living-file policy, an agent needs nothing else to call this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description adds an important interaction not in the schema — that options.date only sets the bullet's date while placement follows options.position — and shows it in a backdated-entry example. The remaining emphasis on single-line/escaping constraints duplicates what the schema and the Obsidian note already say, so it falls short of 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource: "Append a dated entry to a section of a memory file in About Me/." It names the scope (memory files under About Me/), the unit of write (a dated entry, in a named section), and the idempotency rule, which separates it from the generic note-writing siblings vault_write_note and vault_patch_note.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is an explicit "When to use" (recording a preference, principle, opinion, or fact about the user), a prerequisite (call vault_list_memory_files first and reuse existing file/section names) and an exclusions section routing non-memory notes to vault_write_note. The `entry-policy: living` paragraph also names vault_delete_memory as the correct sibling for pruning.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_update_propertiesUpdate PropertiesADestructiveIdempotent
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 (here null writes an empty property, and {} removes it). 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 merge refuses a block it cannot keep: invalid YAML, a list, a single value, or a YAML tag; 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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path to the note, including the ".md" extension. Use the exact letter case. | |
| replace | No | Replace instead of merging (default: false). | |
| properties | Yes | Properties to merge, or with replace the complete new set. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare destructiveHint=true and idempotentHint=true, but the description goes well beyond them: it says exactly what is destroyed under each mode (null deletes a key; replace wipes the whole block where null writes an empty property and {} removes it), that the note body is preserved, that arrays are replaced not appended, and that concurrent writes require re-read-and-retry. The enumerated error strings map directly to recovery actions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded correctly — merge/replace semantics come before examples, usage, and errors, and every section is actionable. The error catalogue is long (eight entries) and could be compressed for an agent that will simply read the returned message, which keeps it short of a 5 despite zero filler elsewhere.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 compensates by stating the two return strings. Path constraints (.md extension, vault-relative, no hidden segments) and the Obsidian type-consistency warning cover the remaining failure modes for a destructive, nested-object mutation tool. Nothing needed 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, which sets a baseline of 3, but the description adds semantics the schema cannot convey: null as a delete sentinel, {} as a block remover, the replace flag's whole-set behaviour, and array replacement rather than append. These are non-obvious semantics for what is effectively an untyped object parameter (additionalProperties: {}), so the added value is substantial.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (update a note's frontmatter properties) and immediately distinguishes the two operating modes (shallow merge vs. replace). It also names the sibling it is not used for (vault_write_note for creating notes or replacing bodies), so an agent can pick it out of a 30-tool sibling list 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
An explicit "When to use" line (changing tags, status, type, or any property without rewriting the body) plus a "Prefer vault_write_note when…" exclusion and a recommended read-first step (vault_read_note with properties_only: true) for the repair case. Alternative selection is fully specified rather than inferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_update_taskUpdate TaskADestructive
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
When to use: Any change to an existing task. Use vault_list_tasks first to get identification fields (path + block_id or line). For creating a new task, use vault_create_task instead.
Parameters:
Exactly one of block_id or line is required to identify the task. A Kanban sub-task this server created has no block_id, so re-read its line with vault_list_tasks just before an update by line. After a write, wait a few seconds for the listing to catch up.
At least one change is required. Clearing is always explicit null — omitting a field leaves it untouched.
status side effects: Kanban: "done" moves the card and everything nested under it to the done lane (the lane whose first line is Complete, else the heading "Done", exact case), leaving nested checkboxes as they are; a sub-task stays under its parent. Recurring (🔁), on "done" only: spawns the next occurrence, dates advanced per the rule, directly above the completed task, which keeps its nested lines. When the Tasks plugin's settings put the next recurrence on the line below, the spawn goes on the line directly below the task instead, so the task's nested lines then sit under the spawn. The spawn stays in the source lane with no block_id, 🆔, or ⛔ — follow up with assign_block_id on next_occurrence.line, except on an indented line of a Kanban board. Completing by line is NOT idempotent for recurring tasks (the spawn occupies the old line); prefer block_id. Delete (🏁 delete / [onCompletion:: delete]), on "done": removes the task line and its children instead of moving to done. With 🔁 + 🏁, the next occurrence is still created, and the completed task is removed.
recurrence: a rule ending "when done" bases the next occurrence on the completion day. When set together with status "done", the new rule governs the next occurrence. Setting recurrence to null while completing removes the rule and completes without spawning.
on_completion: passed together with status, the submitted value governs the delete decision — "keep" while completing a "delete" task prevents the deletion.
position: applies to a heading move or an auto-done-lane move; otherwise it reorders the task within its current lane. 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" — the message states the rule
"blockId ... not found" / "blockId ... is inside a fenced code block or comment" — no task line outside a fenced code block or %% %% comment ends with that ^id; check the id with vault_list_tasks, or target by line
"blockId ... matches N task lines" — 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" — the message lists the fields that count
"cannot move a sub-task to a heading" / "cannot reposition a sub-task" — a heading or position on a task nested under another task (depth > 0 in vault_list_tasks); sub-tasks move with their parent, so move or reposition the parent instead
"cannot reorder a task that sits above the first heading" — position without a heading on a task before the first section heading; the message names the fix
"cannot reorder within "X" — the heading appears N times" / "cannot place at position P under "X" — the heading appears N times" — the heading name is duplicated in the note: the task's own heading for a reorder in place, the one in heading for a move; for a cross-lane move use "top" or "bottom" (they place under the first match), or rename one section to make it unique
"heading "X" not found; available: ..." — the message lists the note's headings
"multiple done lanes detected" / "no done lane detected" — status "done" on a Kanban board with more than one lane whose first line is Complete, or with none and no "Done" heading; pass heading to pick the lane
"blockId ... already exists" / "blockId ... contains invalid characters" — assign_block_id must be unique in the note and match [a-zA-Z0-9-]+
"assignBlockId is not allowed" / "description ends in a block ID" / "subtask ... ends in a block ID" — a new ^id on an indented line of a Kanban board; leave the line without an id and target it by line, and drop any trailing ^id from the text
"invalid date: …" — the message names the expected format
"description cannot be empty" / "addSubtasks cannot contain an empty item" / "description must be a single line" / "addSubtasks items must be a single line" — pass visible text on a single line
"taskId ... contains invalid characters" / "dependsOn entry ... contains invalid characters" — the message names the allowed characters
"unrecognized recurrence rule ..." — the message gives example rules
"concurrent write in progress" — another write to this note is in flight; retry
"ENAMETOOLONG: …", "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. 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.
Returns: JSON { path, line, description, block_id, heading, subtasks, next_occurrence, changes, advisories, on_completion_applied }.
line is the task's final 1-based line number in the file; after an on_completion delete, the line the task occupied before removal.
description is the current text, and block_id and heading reflect the task after the update; block_id is omitted when the task has none, heading when the task sits above the first heading.
subtasks lists each checklist item add_subtasks wrote as { line, description } (omitted when none were added); checklist items are written without a block_id, so line is the handle for a follow-up update.
next_occurrence is present only when a completion spawned a recurring task's next occurrence: { line, description, due?, scheduled?, start? } with only the dates the occurrence has.
changes lists every field applied as "field: before → after", with "(none)" for an absent value; an add_subtasks change is listed under "subtasks", a spawn under "next_occurrence", and a 🏁 delete under "on_completion".
advisories (omitted when there are none) lists one sentence for each: a stored line that parses back differently than submitted (see Obsidian syntax above), a duplicate tag removed from the line, or a completed recurring task whose rule yields no next occurrence (unreadable rule text, or a finite rule with no occurrences left).
on_completion_applied is "delete", present only when the effective on_completion was delete (already on the task or set in the same call) and the task was transitioned to done.
| Name | Required | Description | Default |
|---|---|---|---|
| due | No | Due date (YYYY-MM-DD) to set, or null to clear. | |
| line | No | 1-based line number from vault_list_tasks. Fragile if the file changed since the query. | |
| path | Yes | Vault-relative path to the note containing the task (must end in ".md"). Use the exact letter case. | |
| start | No | Start date (YYYY-MM-DD) to set, or null to clear. | |
| format | No | 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. | |
| status | No | 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. | |
| created | No | Created date (YYYY-MM-DD) to set or clear, for corrections. vault_create_task stamps it on every new task. | |
| heading | No | Target 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_id | No | Tasks plugin 🆔 identifier to set, or null to clear — letters, digits, hyphens, and underscores; separate from block_id. | |
| block_id | No | Stable task identifier — the ^block-id at the end of the task line, without the ^. Preferred over line. | |
| position | No | 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). 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. | |
| priority | No | Priority signifier to set, or null to remove it. | |
| scheduled | No | Scheduled date (YYYY-MM-DD) to set, or null to clear. | |
| depends_on | No | Tasks plugin ⛔ dependency IDs to set (non-empty), or null to clear. | |
| recurrence | No | 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. | |
| description | No | New task description text; metadata fields and block_id are preserved. | |
| add_subtasks | No | Checklist items to append, one indented todo line each, under the task's existing items. 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_completion | No | Tasks 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_id | No | 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. |
TDQS
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 far beyond by explaining what gets destroyed (🏁 delete removes the task line and children), that Kanban 'done' cascades to nested cards, that recurring completions spawn a sibling and that completing by line is NOT idempotent, and that concurrent writes are rejected. It never contradicts the destructive/non-idempotent annotations and reinforces them with concrete mechanisms.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is front-loaded and well-sectioned (capability, examples, when-to-use, parameters, errors, returns), and the examples are genuinely instructive. However, the ~25-entry error catalogue with per-error repair instructions, plus the Obsidian syntax and full return-value walkthrough, pushes the text well past what most agents need at selection time.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 covers identification, mutation preconditions, side effects, error recovery, and a full enumeration of the returned JSON fields. Nothing an agent needs in order to call it correctly, or to interpret the result, is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 semantics the schema does not carry: exactly one of block_id or line, that clearing requires explicit null and omission leaves a field untouched, the interaction between recurrence and status 'done', how on_completion overrides the stored 🏁 value, and how position differs from a plain 'bottom' append. This is genuine meaning beyond the field descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence gives a specific verb (update) and resource (an existing task) plus the exhaustive list of writable fields, and the 'When to use' section explicitly separates it from vault_create_task and vault_list_tasks. An agent can distinguish it from every sibling without reading the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states 'Any change to an existing task', directs the agent to vault_list_tasks first for identification fields, names vault_create_task for creation, and names vault_create_task with parent_block_id for full sub-tasks. It also gives explicit exclusions ('cannot move a sub-task to a heading'), which is stronger than typical.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
vault_write_noteWrite NoteADestructive
Create a markdown note, or with overwrite: true replace an existing note's body; the old body's text is lost unless the new body includes it. Properties merge with any existing properties when overwriting (new keys added, matching keys overwritten, keys set to null removed, unmentioned keys preserved); overwriting without properties keeps the existing property values. A new note leaves out keys set to null.
Example: vault_write_note({ path: "Projects/notes.md", body: "# Notes\n\nProject notes here.", properties: { tags: ["project"], type: "project" } }) Example: vault_write_note({ path: "Projects/notes.md", body: "Updated content.", overwrite: true })
When to use: Creating a new note, or replacing a whole note's body. Prefer vault_patch_note or vault_replace_in_note to change part of a note. Prefer vault_update_properties for property-only edits (no body round-trip). Prefer vault_update_memory for appending dated entries to About Me/ memory files.
Errors:
"note already exists" — set overwrite: true to replace it
"path must end in …" — add the .md extension
"cannot write note …: that path is not a file" — a folder already has this name; choose another path
"absolute path blocked" / "path traversal blocked" / "hidden path blocked" — 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" — remove the character the message names
"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 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: 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 ".
| Name | Required | Description | Default |
|---|---|---|---|
| body | Yes | Markdown body content — do not include frontmatter fences (---); use the properties parameter instead. | |
| path | Yes | 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. | |
| overwrite | No | Allow overwriting an existing note (default: false). | |
| properties | No | Properties to merge. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare destructiveHint=true, and the description substantiates exactly what is destroyed ('the old body's text is lost unless the new body includes it') and how properties survive a body overwrite via merge/unmentioned-key-preservation rules. It also discloses non-obvious behavior an agent would otherwise learn only by failing: concurrent-write errors requiring retry, hidden/absolute path blocking, and properties-block repair failures.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Core mutation semantics are front-loaded before examples, guidance, and errors, which is the right ordering. The error catalogue is unusually long (nine entries) and, while each line is terse and actionable, it stretches the definition beyond what most callers need for selection; a slightly trimmed list would earn full marks.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive write tool with no output schema, the description still states the return value ('Wrote <path>'), covers the overwrite/merge contract, path rules, Obsidian markdown pitfalls, and failure recovery. 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but the description adds semantics the schema does not carry: overwrite merges properties rather than replacing them, null values remove keys, omitting properties preserves existing values on overwrite yet drops null keys on creation, and property type consistency matters for later queries. That is real added meaning for the nested properties object, not restatement.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States the precise verb and resource ('Create a markdown note') and immediately defines the overwrite variant as replacing an existing note's body. It explicitly distinguishes itself from four named siblings (vault_patch_note, vault_replace_in_note, vault_update_properties, vault_update_memory), so an agent can route correctly 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides an explicit 'When to use' block plus three 'Prefer X' alternatives each paired with the condition that selects them (partial edits, property-only edits, dated memory entries). No inference required.
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.
14 tool updates
v0.55.4- Changed
vault_create_task1 field changed- changed
Input schema / properties / depends_on / descriptionPrevious value: -"Tasks plugin ⛔ dependency IDs (🆔 values of other tasks), not checked against existing tasks. Non-empty; omit when there are no dependencies."New value: +"Tasks plugin ⛔ dependency IDs (🆔 values of other tasks), not checked against existing tasks. Omit when there are no dependencies."
- Changed
vault_delete_span2 fields changed- changed
Input schema / properties / end_anchor / descriptionPrevious value: -"Short substring that identifies the LAST line of the block (case-sensitive). Omit to delete just the single line containing start_anchor."New value: +"Short substring of the block's LAST line, a body line (case-sensitive). Omit to delete just the single line containing start_anchor." - changed
Input schema / properties / start_anchor / descriptionPrevious value: -"Short, unique substring that identifies the first line of the block (case-sensitive). Pick a brief fragment — do not paste the whole block."New value: +"Short, unique substring of the block's first line, a body line (case-sensitive)."
- Changed
vault_get_memory2 fields changed- changed
Input schema / properties / on_or_after / descriptionPrevious value: -"Inclusive date filter (YYYY-MM-DD). Requires a file."New value: +"Inclusive date filter (YYYY-MM-DD)." - changed
Input schema / properties / section / descriptionPrevious 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."New value: +"H2 section heading (e.g. \"Decision heuristics (newest first)\"). Matched case-insensitively, with or without the \"(newest first)\" suffix."
- Changed
vault_insert_at_anchor2 fields changed- changed
Input schema / properties / anchor / descriptionPrevious value: -"Short, unique substring on the line to insert next to (case-sensitive). Pick a brief fragment — do not paste the whole line."New value: +"Short, unique substring of the body line to insert next to (case-sensitive)." - changed
Input schema / properties / position / descriptionPrevious value: -"\"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."New value: +"\"before\" inserts above the anchor line, \"after\" below it; the anchor line itself is never changed."
- Changed
vault_list_tasks3 fields changed- changed
Input schema / properties / sort_by / descriptionPrevious 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."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). \"position\" sorts by file path then line number." - changed
Input schema / properties / sort_direction / descriptionPrevious value: -"Sort 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."New value: +"Sort direction. Default per field: \"asc\" for due/scheduled/priority/position, \"desc\" for start/created/done/note_mtime. When a date sort cascades (see sort_by), each field uses its own default direction; an explicit value overrides all fields uniformly." - changed
Input schema / properties / status / descriptionPrevious value: -"Status filter, OR-combined (default \"not_done\" = todo + in_progress, excluding done and cancelled). \"all\" includes every status."New value: +"Status filter, OR-combined (default \"not_done\" = todo + in_progress)."
- Changed
vault_memory_recall1 field changed- changed
Input schema / properties / file / descriptionPrevious 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."New value: +"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."
- Changed
vault_move_note1 field changed- changed
Input schema / properties / new_path / descriptionPrevious 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."New value: +"Destination vault-relative path (e.g. \"Projects/Spec.md\"). Must end in .md; parent folders are created as needed. Use the exact letter case of existing folders; a different case can create a second folder."
- Changed
vault_read_note3 fields changed- changed
Input schema / properties / heading_level / descriptionPrevious value: -"Heading level (1-6) for disambiguation when multiple headings share the same text; only applies with heading"New value: +"Heading level (1-6) for disambiguation when multiple headings share the same text" - changed
Input schema / properties / outline / descriptionPrevious 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."New value: +"If true, returns { bytes, modified, leading_callout?, leading_content?, headings } as JSON instead of body content. 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." - changed
Input schema / properties / start_line / descriptionPrevious 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)."New value: +"First line to return, 1-based (default 1)."
- Changed
vault_recent_notes1 field changed- changed
Input schema / properties / sort_by / descriptionPrevious value: -"Timestamp to sort by, newest first (default \"modified\")"New value: +"Timestamp to sort by (default \"modified\")"
- Changed
vault_replace_span3 fields changed- changed
Input schema / properties / content / descriptionPrevious value: -"Replacement content (one or more lines) — replaces every line of the matched span. Must be non-empty."New value: +"Replacement content (one or more lines)." - changed
Input schema / properties / end_anchor / descriptionPrevious value: -"Short substring that identifies the LAST line of the block (case-sensitive). Omit to replace just the single line containing start_anchor."New value: +"Short substring of the block's LAST line, a body line (case-sensitive). Omit to replace just the single line containing start_anchor." - changed
Input schema / properties / start_anchor / descriptionPrevious value: -"Short, unique substring that identifies the first line of the block (case-sensitive). Pick a brief fragment — do not paste the whole block."New value: +"Short, unique substring of the block's first line, a body line (case-sensitive)."
- Changed
vault_search_by_folder1 field changed- changed
Input schema / properties / recursive / descriptionPrevious value: -"Include subfolders (default: true); false lists only the folder's top level"New value: +"Include subfolders (default: true)"
- Changed
vault_search_by_tag1 field changed- changed
Input schema / properties / tag / descriptionPrevious value: -"Tag, \"#\" optional (e.g. \"project\", \"session-log\"). Nested tags use \"/\" (e.g. \"project/vault-cortex\")."New value: +"Tag, \"#\" optional (e.g. \"project\", \"session-log\", \"project/vault-cortex\")"
- Changed
vault_update_memory1 field changed- changed
Input schema / properties / entry / descriptionPrevious value: -"Raw entry text — a single line; the server prepends \"- **YYYY-MM-DD**: \" automatically. Do not include the date or bullet prefix."New value: +"Raw entry text — a single line, without the \"- **YYYY-MM-DD**: \" prefix, which the server prepends."
- Changed
vault_update_task3 fields changed- changed
Input schema / properties / add_subtasks / descriptionPrevious 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."New value: +"Checklist items to append, one indented todo line each, under the task's existing items. 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." - changed
Input schema / properties / description / descriptionPrevious value: -"New task description text. Replaces the existing description; metadata fields and block_id are preserved."New value: +"New task description text; metadata fields and block_id are preserved." - changed
Input schema / properties / position / descriptionPrevious 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."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). 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."
10 tool updates
v0.55.1- Changed
vault_delete_memory2 fields changed- changed
Input schema / properties / date / descriptionPrevious 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\")." - changed
Input schema / properties / entry / descriptionPrevious 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."
- Changed
vault_get_daily_note1 field changed- changed
Input schema / properties / date / descriptionPrevious 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."
- Changed
vault_get_memory2 fields changed- changed
Input schema / properties / on_or_after / descriptionPrevious 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." - changed
Input schema / properties / section / descriptionPrevious 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."
- Changed
vault_list_property_values1 field changed- changed
Input schema / properties / key / descriptionPrevious 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\")."
- Changed
vault_memory_recall1 field changed- changed
Input schema / properties / query / descriptionPrevious 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."
- Changed
vault_patch_note1 field changed- changed
Input schema / properties / operation / descriptionPrevious value: -"append | prepend | replace | insert_before."New value: +"The edit to apply."
- Changed
vault_replace_in_note1 field changed- changed
Input schema / properties / old_text / descriptionPrevious value: -"Exact text to find (case-sensitive)."New value: +"Exact text to find, no regex (case-sensitive)."
- Changed
vault_search1 field changed- changed
Input schema / properties / include_leading_callout / descriptionPrevious 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."
- Changed
vault_update_memory1 field changed- changed
Input schema / properties / entry / descriptionPrevious 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."
- Changed
vault_update_task1 field changed- changed
Input schema / properties / status / descriptionPrevious 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."
31 tool updates
v0.54.16- Changed
vault_create_task1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_delete_memory1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_delete_note1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_delete_span3 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / end_anchor / descriptionPrevious 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." - changed
Input schema / properties / start_anchor / descriptionPrevious 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."
- Changed
vault_find_orphans1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_get_backlinks1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_get_daily_note1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_get_memory1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_get_outgoing_links1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_insert_at_anchor1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_list_files1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_list_notes1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_list_property_keys1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_list_property_values1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_list_tasks7 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / cancelled / additionalPropertiesAdded value: +false - added
Input schema / properties / created / additionalPropertiesAdded value: +false - added
Input schema / properties / done / additionalPropertiesAdded value: +false - added
Input schema / properties / due / additionalPropertiesAdded value: +false - added
Input schema / properties / scheduled / additionalPropertiesAdded value: +false - added
Input schema / properties / start / additionalPropertiesAdded value: +false
- Changed
vault_memory_recall1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_move_note1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_patch_note2 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / content / descriptionPrevious value: -"Markdown content to insert."New value: +"Markdown content to insert, or the section's new body for replace."
- Changed
vault_read_file3 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / path / descriptionPrevious 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." - changed
Input schema / properties / raw / descriptionPrevious 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."
- Changed
vault_read_note1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_recent_notes1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_replace_in_note2 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / old_text / descriptionPrevious 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)."
- Changed
vault_replace_span3 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / end_anchor / descriptionPrevious 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." - changed
Input schema / properties / start_anchor / descriptionPrevious 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."
- Changed
vault_search8 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / filters / additionalPropertiesAdded value: +false - added
Input schema / properties / filters / properties / created / additionalPropertiesAdded value: +false - added
Input schema / properties / filters / properties / modified / additionalPropertiesAdded value: +false - changed
Input schema / properties / filters / properties / modified / descriptionPrevious 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" - changed
Input schema / properties / filters / properties / related / descriptionPrevious 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]]\")" - changed
Input schema / properties / filters / properties / type / descriptionPrevious 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\")" - changed
Input schema / properties / snippet_tokens / descriptionPrevious value: -"Snippet length in words (default 30)"New value: +"Snippet length in words, not model tokens (default 30)"
- Changed
vault_search_by_folder1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_search_by_property1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_search_by_tag1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_update_memory2 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / options / additionalPropertiesAdded value: +false
- Changed
vault_update_properties1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
vault_update_task3 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / on_completion / descriptionPrevious 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." - changed
Input schema / properties / recurrence / descriptionPrevious 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."
- Changed
vault_write_note1 field changed- added
Input schema / additionalPropertiesAdded value: +false
11 tool updates
v0.54.13- Changed
vault_create_task9 fields changed- changed
Input schema / properties / block_id / descriptionPrevious 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." - changed
Input schema / properties / depends_on / descriptionPrevious 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." - changed
Input schema / properties / heading / descriptionPrevious 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." - changed
Input schema / properties / on_completion / descriptionPrevious 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." - changed
Input schema / properties / position / descriptionPrevious 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\")." - changed
Input schema / properties / priority / descriptionPrevious 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." - changed
Input schema / properties / subtasks / descriptionPrevious 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." - changed
Input schema / properties / task_id / descriptionPrevious 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." - changed
Input schema / requiredPrevious value: -[ - "path", - "description", - "block_id" -]New value: +[ + "path", + "description" +]
- Changed
vault_find_orphans1 field changed- changed
Input schema / properties / exclude_folders / descriptionPrevious 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\")"
- Changed
vault_list_tasks1 field changed- changed
Input schema / properties / tag / descriptionPrevious 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"
- Changed
vault_patch_note2 fields changed- changed
Input schema / properties / content / descriptionPrevious 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." - changed
Input schema / properties / include_children / descriptionPrevious 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."
- Changed
vault_recent_notes1 field changed- changed
Input schema / properties / sort_by / descriptionPrevious value: -"Sort order (default \"modified\")"New value: +"Timestamp to sort by, newest first (default \"modified\")"
- Changed
vault_search1 field changed- changed
Input schema / properties / filters / properties / tags / descriptionPrevious value: -"Require all listed tags (AND — every tag must be present)"New value: +"Tags, \"#\" optional (e.g. [\"reference\"])"
- Changed
vault_search_by_property3 fields changed- changed
Input schema / properties / key / descriptionPrevious 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\")." - changed
Input schema / properties / limit / descriptionPrevious value: -"Max results (default 20)"New value: +"Max results (default 20, no upper cap)" - changed
Input schema / properties / value / descriptionPrevious 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\")."
- Changed
vault_search_by_tag3 fields changed- changed
Input schema / properties / exact / descriptionPrevious value: -"Exact match only (default: false, prefix match)"New value: +"Match only the tag itself (default: false)" - added
Input schema / properties / limitAdded value: +{ + "default": 20, + "description": "Max results (default 20, no upper cap)", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / tag / descriptionPrevious 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\")."
- Changed
vault_update_properties2 fields changed- changed
Input schema / properties / properties / descriptionPrevious value: -"Properties to merge; a null value deletes that key."New value: +"Properties to merge, or with replace the complete new set." - added
Input schema / properties / replaceAdded value: +{ + "default": false, + "description": "Replace instead of merging (default: false).", + "type": "boolean" +}
- Changed
vault_update_task10 fields changed- changed
Input schema / properties / add_subtasks / descriptionPrevious 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." - added
Input schema / properties / assign_block_id / anyOfAdded value: +[ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } +] - changed
Input schema / properties / assign_block_id / descriptionPrevious 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." - removed
Input schema / properties / assign_block_id / minLengthRemoved value: -1 - removed
Input schema / properties / assign_block_id / typeRemoved value: -"string" - changed
Input schema / properties / created / descriptionPrevious 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." - changed
Input schema / properties / format / descriptionPrevious 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." - changed
Input schema / properties / position / descriptionPrevious 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." - changed
Input schema / properties / status / descriptionPrevious 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." - changed
Input schema / properties / task_id / descriptionPrevious 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."
- Changed
vault_write_note2 fields changed- changed
Input schema / properties / overwrite / descriptionPrevious value: -"Allow overwriting an existing note (default: false — errors if file exists)."New value: +"Allow overwriting an existing note (default: false)." - changed
Input schema / properties / properties / descriptionPrevious value: -"Optional properties to merge; a null value deletes that key."New value: +"Properties to merge."
18 tool updates
v0.54.9- Changed
vault_create_task5 fields changed- changed
Input schema / properties / heading / descriptionPrevious 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." - changed
Input schema / properties / parent_block_id / descriptionPrevious 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." - changed
Input schema / properties / parent_line / descriptionPrevious 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." - changed
Input schema / properties / position / descriptionPrevious 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." - changed
Input schema / properties / recurrence / descriptionPrevious 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."
- Changed
vault_delete_memory1 field changed- changed
Input schema / properties / entry / descriptionPrevious 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."
- Changed
vault_delete_span1 field changed- changed
Input schema / properties / end_anchor / descriptionPrevious 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."
- Changed
vault_find_orphans1 field changed- changed
Input schema / properties / exclude_folders / descriptionPrevious 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\")"
- Changed
vault_insert_at_anchor1 field changed- changed
Input schema / properties / content / descriptionPrevious 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)."
- Changed
vault_list_files2 fields changed- added
Input schema / properties / extensions / minItemsAdded value: +1 - changed
Input schema / properties / folder / descriptionPrevious 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."
- Changed
vault_list_tasks3 fields changed- added
Input schema / properties / priority / minItemsAdded value: +1 - changed
Input schema / properties / sort_by / descriptionPrevious 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." - changed
Input schema / properties / status / descriptionPrevious 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."
- Changed
vault_memory_recall1 field changed- changed
Input schema / properties / limit / descriptionPrevious 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)."
- Changed
vault_patch_note3 fields changed- changed
Input schema / properties / content / descriptionPrevious 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)." - changed
Input schema / properties / heading / descriptionPrevious 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." - changed
Input schema / properties / operation / descriptionPrevious 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."
- Changed
vault_read_note3 fields changed- changed
Input schema / properties / heading / descriptionPrevious 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." - changed
Input schema / properties / limit / descriptionPrevious 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)." - changed
Input schema / properties / start_line / descriptionPrevious 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)."
- Changed
vault_replace_span2 fields changed- changed
Input schema / properties / content / descriptionPrevious 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." - changed
Input schema / properties / end_anchor / descriptionPrevious 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."
- Changed
vault_search5 fields changed- changed
Input schema / properties / filters / descriptionPrevious value: -"Optional structured filters — all conditions AND-combine with each other and with the text query"New value: +"Optional structured filters" - changed
Input schema / properties / filters / properties / created / descriptionPrevious 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" - changed
Input schema / properties / include_leading_callout / descriptionPrevious 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." - changed
Input schema / properties / query / descriptionPrevious value: -"Search query text — unquoted terms use implicit AND with stemming; wrap in double quotes for exact phrases"New value: +"Search query text" - changed
Input schema / properties / snippet_tokens / descriptionPrevious value: -"Snippet length in tokens (default 30)"New value: +"Snippet length in words (default 30)"
- Changed
vault_search_by_folder1 field changed- changed
Input schema / properties / recursive / descriptionPrevious value: -"Include subfolders (default: true)"New value: +"Include subfolders (default: true); false lists only the folder's top level"
- Changed
vault_search_by_property1 field changed- changed
Input schema / properties / value / descriptionPrevious 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."
- Changed
vault_update_memory1 field changed- changed
Input schema / properties / options / properties / date / descriptionPrevious value: -"ISO YYYY-MM-DD date (defaults to today)"New value: +"ISO YYYY-MM-DD date (defaults to today, server timezone)"
- Changed
vault_update_properties1 field changed- changed
Input schema / properties / properties / descriptionPrevious 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."
- Changed
vault_update_task3 fields changed- changed
Input schema / properties / position / descriptionPrevious 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." - changed
Input schema / properties / recurrence / descriptionPrevious 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." - changed
Input schema / properties / status / descriptionPrevious 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."
- Changed
vault_write_note1 field changed- changed
Input schema / properties / properties / descriptionPrevious 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."
23 tool updates
v0.54.7- Changed
vault_create_task1 field changed- changed
Input schema / properties / path / descriptionPrevious 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."
- Changed
vault_delete_memory2 fields changed- added
Input schema / properties / entry / minLengthAdded value: +1 - changed
Input schema / properties / file / descriptionPrevious value: -"Memory file name without .md (e.g. \"Principles\")"New value: +"Memory file name without .md (e.g. \"Principles\"). Use the exact letter case."
- Changed
vault_delete_note2 fields changed- changed
Input schema / properties / path / descriptionPrevious 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." - changed
Input schema / properties / prune_empty_folders / descriptionPrevious 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."
- Changed
vault_delete_span1 field changed- changed
Input schema / properties / path / descriptionPrevious 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."
- Changed
vault_get_daily_note1 field changed- added
Input schema / properties / date / minLengthAdded value: +1
- Changed
vault_get_memory1 field changed- changed
Input schema / properties / file / descriptionPrevious 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."
- Changed
vault_insert_at_anchor1 field changed- changed
Input schema / properties / path / descriptionPrevious 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."
- Changed
vault_list_notes4 fields changed- changed
Input schema / properties / folder / descriptionPrevious value: -"Folder path prefix (e.g. \"About Me\", \"Projects\"). Includes all subfolders."New value: +"Vault-relative folder to list (e.g. \"About Me\", \"Projects\")." - added
Input schema / properties / folder / minLengthAdded value: +1 - changed
Input schema / properties / glob / descriptionPrevious 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\")." - added
Input schema / properties / glob / minLengthAdded value: +1
- Changed
vault_list_property_values2 fields changed- changed
Input schema / properties / folder / descriptionPrevious value: -"Restrict to a folder prefix (e.g. \"Projects\")"New value: +"Restrict to a folder (e.g. \"Projects\")" - changed
Input schema / properties / limit / descriptionPrevious value: -"Max values to return (default 50). Increase for high-cardinality properties."New value: +"Max values to return (default 50)."
- Changed
vault_list_tasks2 fields changed- changed
Input schema / properties / folder / descriptionPrevious 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\")" - changed
Input schema / properties / path / descriptionPrevious value: -"Restrict to one note (vault-relative path ending \".md\")"New value: +"Restrict to one note (vault-relative path ending \".md\", case-sensitive)"
- Changed
vault_memory_recall1 field changed- changed
Input schema / properties / file / descriptionPrevious 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."
- Changed
vault_move_note3 fields changed- changed
Input schema / properties / new_path / descriptionPrevious 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." - changed
Input schema / properties / old_path / descriptionPrevious 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." - changed
Input schema / properties / prune_empty_folders / descriptionPrevious 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."
- Changed
vault_patch_note1 field changed- changed
Input schema / properties / path / descriptionPrevious 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."
- Changed
vault_read_file4 fields changed- changed
Input schema / properties / limit / descriptionPrevious 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)." - changed
Input schema / properties / path / descriptionPrevious 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." - changed
Input schema / properties / raw / descriptionPrevious 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." - changed
Input schema / properties / start_line / descriptionPrevious 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)."
- Changed
vault_read_note1 field changed- changed
Input schema / properties / path / descriptionPrevious 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."
- Changed
vault_replace_in_note1 field changed- changed
Input schema / properties / path / descriptionPrevious 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."
- Changed
vault_replace_span1 field changed- changed
Input schema / properties / path / descriptionPrevious 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."
- Changed
vault_search1 field changed- changed
Input schema / properties / filters / properties / folder / descriptionPrevious value: -"Restrict to a folder path prefix (e.g. \"Projects\")"New value: +"Restrict to a folder (e.g. \"Projects\")"
- Changed
vault_search_by_property2 fields changed- changed
Input schema / properties / folder / descriptionPrevious value: -"Restrict to a folder prefix (e.g. \"Projects\")"New value: +"Restrict to a folder (e.g. \"Projects\")" - changed
Input schema / properties / limit / descriptionPrevious value: -"Max results (default 20). Increase for broad metadata queries."New value: +"Max results (default 20)"
- Changed
vault_update_memory1 field changed- changed
Input schema / properties / file / descriptionPrevious 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."
- Changed
vault_update_properties1 field changed- changed
Input schema / properties / path / descriptionPrevious 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."
- Changed
vault_update_task1 field changed- changed
Input schema / properties / path / descriptionPrevious 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."
- Changed
vault_write_note1 field changed- changed
Input schema / properties / path / descriptionPrevious 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."
1 tool update
v0.54.1- Changed
vault_get_memory2 fields changed- changed
Input schema / properties / on_or_after / descriptionPrevious 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." - changed
Input schema / properties / section / descriptionPrevious 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."
2 tool updates
v0.54.0- Changed
vault_create_task1 field changed- changed
Input schema / properties / subtasks / descriptionPrevious 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."
- Changed
vault_update_task1 field changed- changed
Input schema / properties / add_subtasks / descriptionPrevious 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."
4 tool updates
v0.53.0- Changed
vault_create_task4 fields changed- added
Input schema / properties / position / anyOfAdded value: +[ + { + "enum": [ + "top", + "bottom" + ], + "type": "string" + }, + { + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" + } +] - changed
Input schema / properties / position / descriptionPrevious 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." - removed
Input schema / properties / position / enumRemoved value: -[ - "top", - "bottom" -] - removed
Input schema / properties / position / typeRemoved value: -"string"
- Changed
vault_get_memory1 field changed- added
Input schema / properties / on_or_afterAdded 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" +}
- Changed
vault_read_note1 field changed- changed
Input schema / properties / outline / descriptionPrevious 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."
- Changed
vault_update_task5 fields changed- added
Input schema / properties / position / anyOfAdded value: +[ + { + "enum": [ + "top", + "bottom" + ], + "type": "string" + }, + { + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" + } +] - removed
Input schema / properties / position / defaultRemoved value: -"top" - changed
Input schema / properties / position / descriptionPrevious 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." - removed
Input schema / properties / position / enumRemoved value: -[ - "top", - "bottom" -] - removed
Input schema / properties / position / typeRemoved value: -"string"
2 tool updates
v0.51.1- Changed
vault_create_task1 field changed- added
Input schema / properties / on_completionAdded value: +{ + "description": "Tasks plugin 🏁 onCompletion action. \"delete\" removes the task line on completion; \"keep\" leaves it in place.", + "enum": [ + "delete", + "keep" + ], + "type": "string" +}
- Changed
vault_update_task3 fields changed- changed
Input schema / properties / add_subtasks / descriptionPrevious 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." - added
Input schema / properties / on_completionAdded 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." +} - changed
Input schema / properties / status / descriptionPrevious 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."
2 tool updates
v0.51.0- Changed
vault_create_task1 field changed- added
Input schema / properties / recurrenceAdded 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" +}
- Changed
vault_update_task1 field changed- added
Input schema / properties / recurrenceAdded 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
Scored across 33 tools
Most tools have distinct resource+action purposes, and descriptions include explicit 'prefer X for Y' routing guidance. However, the search surface (vault_search, vault_search_by_property/tag/folder, vault_recent_notes, vault_list_notes/files) and the body-editing surface (patch_note, replace_in_note, replace_span, delete_span, insert_at_anchor) overlap enough that an agent must read lengthy guidance to choose correctly.
Nearly every tool uses vault_ + verb_noun in snake_case (list_notes, search_by_tag, update_properties, create_task), producing a predictable surface. Minor deviations like vault_memory_recall (noun phrase instead of verb_noun) and vault_find_orphans (find rather than list/search) keep it from perfect consistency.
33 tools exceed the typical 3–15 range and land in 'too many' territory, even acknowledging that the server spans notes, files, search, tasks, memory, links, and daily notes. Several narrow sibling variants (replace_in_note/replace_span/delete_span/insert_at_anchor) could be consolidated to shrink the surface.
Core note lifecycle is fully covered (read/write/patch/replace/delete/move/properties), plus search, backlinks/orphans, files, tasks, memory, and daily notes. Gaps remain: no dedicated task-delete tool, no folder management, no write/delete for non-markdown files, and daily notes can only be read rather than created through a dedicated operation.
Maintenance
Related MCP Connectors
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
Token-efficient MCP memory for Markdown vaults. Tiered search, GraphRAG, AI memories.
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
MCP server for Argo RPG Platform — connects AI assistants to campaign data via OAuth2
Related MCP Servers
- AlicenseAqualityFmaintenanceA 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.321MIT
- AlicenseNot gradedqualityBmaintenanceA 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
- AlicenseAqualityAmaintenanceThe most feature-complete MCP server for Obsidian vaults. 23 tools and 3 resources for search, read, write, tags, link analysis, graph traversal, and canvas support.4155 npm33MIT
- AlicenseNot gradedqualityAmaintenanceMCP server for Obsidian — access your vault from any AI agent, even when your machine is off. Powered by Self-hosted LiveSync.154 npm70MIT