Skip to main content
Glama
aliasunder

Vault Cortex Obsidian MCP Server

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

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

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

What you get

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

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

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

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

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

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

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

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

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

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


Related MCP server: Vault MCP Server (mschuchard)

Quick Start

Local (2 minutes — Docker + your vault folder)

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

npx vault-cortex@latest init

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

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

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

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

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

# 3. Start
docker compose up

Full local guide → (includes Windows setup)

Remote (access from anywhere)

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

Railway

Render

Self-hosted

Deploy on Railway

Deploy to Render

CLI setup →

Account

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

Render with a card on file

A VPS with Docker

Cost

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

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

Whatever your VPS costs

Pick it if

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

A predictable bill matters more than setup polish

You already run a server or want full control

Guide

Railway guide →

Render guide →

Remote guide →

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

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

Self-hosted: your own VPS

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

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

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

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

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

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

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

Connect your MCP client

Setup

Server URL

Local

http://localhost:8000/mcp

Remote (one-click)

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

Remote (self-hosted)

<PUBLIC_URL>/mcp

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

Claude Code:

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

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

The "Add custom connector" dialog only accepts https URLs. With an https PUBLIC_URL, add it directly in the connector dialog; for a localhost server, register it in claude_desktop_config.json through the mcp-remote stdio bridge instead:

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

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

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

See Authentication for both methods and token lifetimes.


How It Works

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

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

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

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

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

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


Keyword search alone fails when your vocabulary doesn't match the vault's — "aspirations" won't find a note about "targets", "coworkers" won't surface your "references" file. In testing against a real vault, 30% of natural-language queries returned zero or tangential results with keywords alone. Hybrid search eliminated those misses — vectors bridge the vocabulary gap, and the reranker rescues intent-heavy queries where neither signal is strong on its own.

Hybrid search combines three ranking signals via Reciprocal Rank Fusion:

  • 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 dumping everything into context. Once you have hundreds of dated entries across multiple files — preferences, principles, communication style, ongoing commitments — reading whole files wastes context on irrelevant material and buries the signal. The memory system is designed for targeted retrieval: agents accumulate knowledge over time and recall exactly what's relevant to the task at hand.

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 recallvault_memory_recall retrieves every relevant entry across all memory files at once, keyword- and semantically-matched, oldest first. Ask "what do I think about X?" and get the current take plus the dated history of how it developed — no need to read entire files or guess which file holds what

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

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

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

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


Tasks

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

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

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

  • Create — add a correctly-formatted task in one call: description, priority, dates, block_id, and checklist sub-items, placed under a heading or nested under a parent task

  • Update — complete, reprioritize, edit the text, set or clear dates, add checklist items, and move tasks between headings in a single call. Marking a task done auto-detects the done lane and stamps the completion date; reversing it removes the date

  • 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

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 actually use:

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

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

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

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

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

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

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


Tools

Category

Tool

Description

Vault CRUD

vault_read_note

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

vault_write_note

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

vault_patch_note

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

vault_replace_in_note

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

vault_delete_span

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

vault_replace_span

Replace a block of lines by short anchors with new content

vault_insert_at_anchor

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

vault_list_notes

List notes with optional glob/folder filter

vault_delete_note

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

vault_move_note

Move or rename a note, rewriting links across the vault

Search

vault_search

Hybrid search with tag/folder/property/date filters

vault_search_by_tag

Find notes by tag (exact or prefix match)

vault_search_by_folder

Browse notes in a folder with metadata

vault_recent_notes

Recently modified or created notes

vault_list_tags

All tags with usage counts

Tasks

vault_list_tasks

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

vault_create_task

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

vault_update_task

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

Memory

vault_get_memory

Read structured memory (file, section, or all)

vault_update_memory

Append a dated entry to a memory section

vault_delete_memory

Remove a specific memory entry by date

vault_list_memory_files

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

vault_memory_recall

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

Properties

vault_list_property_keys

All property keys with sample values

vault_list_property_values

Distinct values for a property key

vault_search_by_property

Find notes by property key-value

vault_update_properties

Add or update properties without touching the body

Links

vault_get_backlinks

Notes linking to a given path

vault_get_outgoing_links

Links from a given note

vault_find_orphans

Notes with no incoming links

Files

vault_read_file

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

vault_list_files

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

Daily Notes

vault_get_daily_note

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


Prompts

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

Prompt

Arguments

What it does

vault-orientation

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

memory-review

file?, max_chars?

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

daily-review

date?, max_chars?

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

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

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


Properties

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

Property

What you can do

title

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

tags

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

type

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

created

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

related

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

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

These are conventions, not requirements — Vault Cortex works with any property schema. Promoted properties just 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. Remote deployments also forward Obsidian Sync's own settings — DEVICE_NAME, SYNC_MODE, CONFLICT_STRATEGY, SYNC_CONFIGS, SYNC_EXCLUDED_FOLDERS, SYNC_FILE_TYPES — documented in the remote guide's configuration table.

Variable

Required?

Default

Description

MCP_AUTH_TOKEN

Yes

Bearer token for authentication (also the JWT signing key)

VAULT_PATH

Local only

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

PUBLIC_URL

Remote only

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

OBSIDIAN_AUTH_TOKEN

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

VAULT_NAME

Remote only

Exact name of your Obsidian vault (case-sensitive)

VAULT_PASSWORD

Remote only

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

STORAGE_ROOT

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

EMBEDDING_ENABLED

true

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

RERANK_MODE

blended

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

MEMORY_ENABLED

true

Set false to fully disable the memory layer — hides memory tools, skips bootstrap, omits memory from server metadata. MEMORY_DIR is ignored when false.

FILE_TOOLS_ENABLED

true

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

READONLY_MODE

false

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

DISABLED_TOOLS

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

MEMORY_DIR

About Me

Vault folder for structured memory files

PROTECTED_PATHS

MEMORY_DIR, daily notes folder

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

ORPHAN_EXCLUDE_FOLDERS

DAILY_NOTES_FOLDER, Templates, MEMORY_DIR

Folders excluded from orphan detection

DAILY_NOTES_FOLDER

from vault config

Sets the folder your daily notes live in. When unset, read from the vault's .obsidian/daily-notes.json, falling back to Daily Notes. See Daily notes.

DAILY_NOTES_FORMAT

from vault config

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

TZ

UTC

IANA timezone for timestamps and daily note resolution

SERVICE_DOCUMENTATION_URL

GitHub repo URL

URL returned in OAuth discovery metadata

LOG_LEVEL

info

Logging verbosity: debug, info, warn, error

LOG_DIR

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

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

LOG_RETENTION_DAYS

90

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

WINDOWS_MODE

false

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

MAX_FILE_BYTES

52428800 (50 MiB)

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

MAX_IMAGE_OUTPUT_BYTES

49152 (48 KiB)

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

MAX_PDF_RENDER_PAGES

5

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

TRASH_RETENTION_DAYS

Local only

30

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

TRUST_PROXY_HOPS

0

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

TRUST_FORWARDED_HOPS

0

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

  • Smart defaultsMEMORY_DIR and the daily notes folder feed the defaults for PROTECTED_PATHS and ORPHAN_EXCLUDE_FOLDERS. Set one of those explicitly only when you want a fully custom list: the value replaces the whole default, daily notes folder included.

    • PROTECTED_PATHS reads the daily notes folder from DAILY_NOTES_FOLDER or .obsidian/daily-notes.json (default Daily Notes).

    • ORPHAN_EXCLUDE_FOLDERS takes it from DAILY_NOTES_FOLDER, else Daily Notes — it doesn't read daily-notes.json.

  • MEMORY_ENABLED=false fully disables the memory layer — memory tools are hidden and the memory folder is not auto-created.

  • FILE_TOOLS_ENABLED=false hides file tools entirely — useful when Obsidian Sync has attachment syncing disabled and no files exist on disk.

  • READONLY_MODE=true hides every vault-writing tool and skips memory folder auto-creation — connected clients can read and search but never edit.

  • DISABLED_TOOLS hides exactly the tools you name — for finer control than the switches above, e.g. keep writes on but remove vault_delete_note and vault_move_note. Availability-keyed cross-references in tool descriptions and prompts adjust automatically.

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

Daily notes

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

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

  • Remote mode receives it through Obsidian Sync's vault configuration syncing. The server pulls it by default (the SYNC_CONFIGS setting in .env), but you'll likely need to enable the push side: Obsidian Settings → Sync → Vault configuration sync, per device. Details: the remote guide's Daily notes section.

When the file isn't available — or you use the Periodic Notes plugin, whose settings it doesn't reflect — set DAILY_NOTES_FOLDER (any vault-relative path: Journal, Planner/Daily) and DAILY_NOTES_FORMAT (same tokens as Obsidian's date format setting: YYYY-MM-DD-dddd, YYYY/MM/DD, MMM D, YYYY, …). You can set one or both — a set value always wins over the config file. Without either source, the server falls back to Daily Notes and YYYY-MM-DD.

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


Data Integrity

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

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

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

  • Path traversal blockedresolveSafePath() 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. On Obsidian Sync deployments deletes are always permanent and sync to every device — recovery is Sync's version history.

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

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

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

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


Authentication

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

Two methods:

Method

Used by

Token format

OAuth 2.1

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

JWT (HS256, 6h)

Static bearer

Claude Code, MCP Inspector, curl

Raw MCP_AUTH_TOKEN

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

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

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

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

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

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


Deployment Options

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

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

Path

What

Guide

Local

Your vault on your machine — free, no cloud

deploy/local/

Remote · one-click

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

deploy/render/ · deploy/railway/

Remote · self-hosted

VPS + Obsidian Sync — access from any device

deploy/remote/

Remote · AWS (SST)

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

DEPLOY.md

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

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

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

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

One-click deploy

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

Community deployments

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

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

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


Development

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

# Tests
npm test

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

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

MCP Inspector — interactive browser UI for testing tools:

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

See CONTRIBUTING.md for the full development setup.


Companion: obsidian-vault skill

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

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

Skill source →


Roadmap

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


Acknowledgments

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

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

Contributing

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

License

MIT

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

Security

Report vulnerabilities privately — see SECURITY.md.

Available Tools

33 tools
vault_create_taskCreate TaskA

Create a correctly-formatted task in one call — description, target heading, dates, priority, block_id, and optional checklist sub-items. The task is always created as [ ] (todo) with ➕ today auto-stamped — starting work is vault_update_task's job. Metadata is written in the format the vault's Tasks plugin is configured for (emoji unless the plugin config says Dataview).

Example: vault_create_task({ path: "TASKS.md", description: "Fix login bug", block_id: "fix-login", heading: "Active", priority: "high", due: "2026-09-15" }) Example: vault_create_task({ path: "TASKS.md", description: "Ship the feature", block_id: "ship-feature", heading: "Up Next", subtasks: ["Design", "Implement", "Test"] }) — card with checklist stages Example: vault_create_task({ path: "TASKS.md", description: "Sub-bug", block_id: "sub-bug", parent_block_id: "fix-login", due: "2026-09-01" }) — full sub-task under a parent identified by block_id Example: vault_create_task({ path: "TASKS.md", description: "Quick fix", block_id: "quick-fix", parent_line: 42 }) — sub-task under a parent identified by line number Example: vault_create_task({ path: "TASKS.md", description: "Urgent fix", block_id: "urgent-fix", heading: "Active", position: "top" }) — insert at the top of a lane instead of the default bottom

When to use: Creating a new task card on a board or in a note. Guarantees correct field ordering (description → priority → 🔁 recurrence → 🏁 onCompletion → ➕ created → 🛫 start → ⏳ scheduled → 📅 due → 🆔 task_id → ⛔ depends_on → ^block_id) so the card round-trips through vault_list_tasks with all fields intact. For lightweight checklist items under an existing card (no metadata), use vault_update_task's add_subtasks param instead.

Parameters:

  • path (required): vault-relative path to the note (must end in ".md"). The note must already exist.

  • description (required): the task text (before metadata fields).

  • block_id (required): the ^block-id for stable identification — letters, digits, and hyphens only. Must be unique within the note.

  • heading: target heading. Required on Kanban boards (notes with kanban-plugin frontmatter); optional on regular notes (omit to append at end of body).

  • parent_block_id / parent_line: the existing task to nest under as a sub-task, identified by its ^block-id or its 1-based line number — the same pair vault_update_task uses (block_id / line). Pass at most one. Either is mutually exclusive with heading — a sub-task lives wherever its parent lives.

  • position: "top" or "bottom" — where within the heading section the task is placed. Defaults to "bottom" (append). Kanban boards with new-card-insertion-method set to "prepend" default to "top" instead. Ignored when no heading or when placing under a parent.

  • priority: "highest" | "high" | "medium" | "low" | "lowest". Omit for normal priority (the plugin ranks "no signifier" between medium and low).

  • recurrence: a Tasks plugin 🔁 rule in natural language ("every week", "every month on the 15th", "every 3 days when done" — "when done" bases the next occurrence on the completion day). Completing the task later spawns its next occurrence automatically.

  • on_completion: "delete" or "keep" — sets the Tasks plugin 🏁 action applied when the task is completed. "delete" removes the task line on completion; "keep" leaves it in place.

  • due / scheduled / start: YYYY-MM-DD dates (calendar-validated). Omit a date rather than guessing — an absent 📅 means "no deadline".

  • task_id: Tasks plugin 🆔 identifier for dependency chains.

  • depends_on: non-empty string array of Tasks plugin ⛔ dependency IDs (🆔 values of other tasks).

  • subtasks: string array of checklist item descriptions — created as indented [ ] lines under the card (no metadata, no block_ids). For full sub-tasks with their own dates, priority, and block_id, make a separate vault_create_task call with parent_block_id.

  • format: "emoji" or "dataview" — overrides the auto-detected Tasks plugin format (emoji when no plugin config is present).

Errors:

  • "note not found" — path does not exist

  • "heading required for Kanban boards" — kanban-plugin note without heading

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

  • "parent task not found" — parent_block_id or parent_line doesn't resolve to a task (message names the blockId or line tried)

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

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

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

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

  • "description is empty" / "dependsOn cannot be empty" / "subtasks cannot contain an empty item" — whitespace-only description, an empty depends_on array, or a whitespace-only checklist item

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

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

  • "unrecognized recurrence rule ..." — the rule text is not Tasks-plugin natural language; written as-is it would silently never recur

  • "invalid date" — a date param fails calendar validation

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

Obsidian syntax: The Tasks plugin reads metadata off the END of a task line, so a trailing run of signifier syntax inside description or subtasks text — an emoji field like "🔁 every week", or a Dataview [key:: value] field, followed only by other recognized task fields — is read back as task metadata rather than text. A signifier followed by ordinary prose stays description text unless the prose matches that field's value grammar — a 🔁 recurrence reads any trailing words as its rule, while a 📅 followed by ordinary words stays description text because the words are not a date. The same interference can change the value an adjacent field reads back with, or make a field appear that was never set, as the 🔁 example shows. The write still succeeds either way; when the stored line would read back differently than submitted, the result carries an advisories array naming each divergence.

Returns: JSON { path, line, description, block_id, heading, subtasks, changes, advisories } — line is the new card's 1-based position; heading is the nearest heading above the new task (omitted when the note has none); subtasks lists each checklist item written as { line, description } (omitted when none) — checklist items carry no block_id, so line is the handle for a follow-up update; changes lists every field written as "field: before → after", with "(none)" for an absent value; 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
dueNoDeadline (📅), YYYY-MM-DD, calendar-validated. Omit when there is no deadline.
pathYesVault-relative path to the note (must end in ".md"). The note must already exist.
startNoEarliest day work can begin (🛫), YYYY-MM-DD, calendar-validated.
formatNoField format. Default: auto-detected from .obsidian/ config, falling back to emoji.
headingNoTarget heading. Required on Kanban boards; optional on regular notes (omit to append at end of body).
task_idNoTasks plugin 🆔 identifier other tasks can name in depends_on.
block_idYesThe ^block-id for stable identification — letters, digits, and hyphens only ([a-zA-Z0-9-]+). Must be unique within the note.
positionNoWhere 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.
priorityNoPriority signifier (🔺⏫🔼🔽⏬). Omit for normal priority — no signifier is written.
subtasksNoChecklist 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.
scheduledNoDay the work is planned for (⏳), YYYY-MM-DD, calendar-validated.
depends_onNoTasks plugin ⛔ dependency IDs (🆔 values of other tasks). Non-empty; omit when there are no dependencies.
recurrenceNoTasks plugin 🔁 rule in natural language (e.g. "every week", "every 2 weeks when done"). Completing the task spawns its next occurrence.
descriptionYesThe task text (before metadata fields).
parent_lineNo1-based line number of an existing task to nest under as a sub-task. Mutually exclusive with parent_block_id and heading. Fragile if the file changed since the line was read.
on_completionNoTasks plugin 🏁 onCompletion action. "delete" removes the task line on completion; "keep" leaves it in place.
parent_block_idNo^block-id (without the ^) of an existing task to nest under as a sub-task. Mutually exclusive with parent_line and heading.

TDQS

A5/5.0
Behavior5/5

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

Annotations are all false, so the description carries the full burden. It discloses that tasks are always created as [ ] (todo) with ➕ today auto-stamped, explains the metadata format auto-detection, provides an exhaustive error list including concurrency, describes the Obsidian syntax parsing quirks, and notes that the write may produce advisories when round-tripping differs. This goes far beyond what 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.

Conciseness5/5

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

The description is long but well-structured with clear sections: intro, examples, 'When to use', parameter details, error list, Obsidian syntax explanation, and return format. Each sentence serves a purpose, and critical information (purpose, examples) is front-loaded. The length is justified given the tool's complexity (17 parameters, multiple edge cases).

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

Completeness5/5

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

Given the high complexity, lack of output schema, and no helpful annotations, the description covers everything an agent needs: required/optional parameters, usage examples, error handling, return JSON structure including advisories, and even the parsing interference phenomenon. It also routes to appropriate sibling tools. Nothing critical is missing.

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

Parameters5/5

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

While schema coverage is 100%, the description adds substantial meaning: it explains field ordering, mutual exclusions (parent_block_id/parent_line vs heading), position default logic per Kanban config, subtasks vs full sub-tasks distinction, format override, and gives five concrete examples mapping parameters to use cases. It clearly adds value beyond the schema.

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

Purpose5/5

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

The description opens with a clear statement of the tool's function: 'Create a correctly-formatted task in one call' with explicit mention of the task components. It distinguishes itself from the sibling vault_update_task by noting that 'starting work is vault_update_task's job' and explicitly directs users to vault_update_task for lightweight checklist items. This level of specificity and sibling differentiation is exemplary.

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

Usage Guidelines5/5

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

The 'When to use' section explicitly states the intended scenario: 'Creating a new task card on a board or in a note.' It also provides clear exclusions by referencing vault_update_task's add_subtasks for lightweight checklist items, and warns against using this tool for starting work. This gives the agent explicit routing guidance with no ambiguity.

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

vault_delete_memoryDelete Memory EntryA
Destructive

Delete a single dated entry from a About Me/ memory file. Both date and entry text are required for exact matching — ensures only the intended entry is removed.

Example: vault_delete_memory({ file: "Opinions", section: "AI tooling & memory (newest first)", date: "2026-05-01", entry: "Prefer X over Y" })

When to use: Removing an entry that was wrong when it was written — a mistake, a misattribution, or something never true. Memory files are append-only by default, so do NOT delete to reflect a change: when a preference or fact has since evolved, append the new state via vault_update_memory (newest-first naturally supersedes). The exception is a file whose frontmatter declares entry-policy: living (check via vault_list_memory_files) — a current-state file where deleting an expired entry is the intended maintenance. Call vault_get_memory(file, section) first to see exact entry text for matching. Prefer vault_update_memory to supersede a changed entry; prefer vault_delete_note for deleting entire non-protected notes.

Parameters:

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

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

Errors:

  • "memory file must not start with a dot" — a dot-prefixed name would target a hidden file; memory files are always visible notes.

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

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

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

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

  • "refusing memory write: … would shrink content" — safety guard blocked a write that would remove more than half the file. Re-read with vault_get_memory to confirm current content before retrying.

Returns: Confirmation message.

ParametersJSON Schema
NameRequiredDescriptionDefault
dateYesISO YYYY-MM-DD date of the entry (e.g. "2026-05-01"). Must match the date shown by vault_get_memory.
fileYesMemory file name without .md (e.g. "Principles")
entryYesExact entry text as shown by vault_get_memory — without the "- **YYYY-MM-DD**: " prefix or bullet. Both date and entry must match for deletion.
sectionYesH2 section heading containing the entry. Matched case-insensitively, with or without the "(newest first)" suffix.

TDQS

A4.9/5.0
Behavior5/5

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

The description fully informs the agent about behavioral traits beyond annotations. It discloses the exact matching requirement, case-insensitive section matching, common errors (ambiguous, no entry, dot-prefix error), the safety guard that blocks writes shrinking content by more than half, and return value. The annotation shows destructiveHint: true, and the description aligns with this by detailing deletion behavior and blockers.

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

Conciseness4/5

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

The description is thorough and front-loaded with the core purpose, but it is somewhat lengthy. Every sentence adds value, but the error details could be slightly compressed. Still, it is well-structured with clear paragraphs for when to use, parameters, and errors.

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

Completeness5/5

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

Given the tool's complexity (4 required parameters, sibling tools for deletion and update), the description is remarkably complete. It covers usage boundaries, error cases, safety blocks, and return values. No output schema exists, but the return is simple ('Confirmation message') and the errors are enumerated, making the agent well-equipped to handle the tool.

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

Parameters5/5

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

The description adds significant meaning beyond the schema. It explains how 'date + entry together uniquely identify the bullet line', notes that identical entries cause ambiguity, details section matching behavior (case-insensitive, ignoring suffix), and provides a full example. Schema coverage is 100% but the description enriches every parameter with context on errors and usage.

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

Purpose5/5

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

The description clearly states 'Delete a single dated entry from an About Me/ memory file'. It identifies the specific verb (delete) and resource (dated entry from a memory file), and distinguishes this from sibling tools like vault_delete_span, vault_delete_note, and vault_update_memory by specifying exact matching and alternative use cases.

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

Usage Guidelines5/5

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

The description provides extensive guidance on when to use and when not to use this tool. It explicitly says 'do NOT delete to reflect a change', advises preferring vault_update_memory for superseding changed entries, and mentions exceptions for 'living' files. It also recommends calling vault_get_memory first and lists alternatives like vault_delete_span for ambiguous cases.

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

vault_delete_noteDelete NoteA
Destructive

Delete a markdown note, honoring the vault's Obsidian "Deleted files" setting (trashOption in .obsidian/app.json) when the vault is served locally. "Move to system trash" — Obsidian's default, and what an absent setting means — moves the note to .trash/ inside the vault (a container has no system trash, and .trash/ is Obsidian's own fallback for that), where the server cleans up its copies after a retention window (TRASH_RETENTION_DAYS, default 30 days; none keeps them forever). "Move to Obsidian trash (.trash folder)" (local) also moves the note to .trash/, kept forever — matching Obsidian. "Permanently delete" (none) removes the note for good. Only notes this server moved to .trash/ under the system setting are subject to the retention window — notes Obsidian itself trashed are never touched. When Obsidian Sync is configured, the trash setting is bypassed and notes are always permanently deleted — recovery is through Sync's version history, not .trash/. After deletion, links to it from other notes become broken (detectable via vault_get_backlinks). Protected paths (About Me/ and the daily notes folder (read from DAILY_NOTES_FOLDER or .obsidian/daily-notes.json, defaulting to Daily Notes/)) are refused.

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.

Behavior: With prune_empty_folders, pruning is best-effort and runs after the delete or trash move — it never fails the call, so the note is always gone from its original path even if a folder can't be removed. If your vault uses Obsidian Sync, deleted notes are recoverable from Sync's version history (1 month on Standard, 12 months on Plus).

Errors:

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

  • "absolute path blocked" — the path starts at the filesystem root; use a vault-relative path

  • "path traversal blocked" — path escapes the vault root; use a vault-relative path

  • "hidden path blocked" — the path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; hidden paths are not deletable, matching Obsidian

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

  • "note not found: …" — the note does not exist; verify the path with vault_list_notes before deleting

  • "cannot move to trash … — 100 collisions in .trash/" — the note's name already exists 100 times in .trash/; clear old trash files to free the name

  • "cannot move to trash …" — the .trash/ move failed (e.g. a plain file blocks a needed directory); the note remains at its original path

  • "cannot delete …" — permanent delete failed (e.g. permissions); the note remains at its original path

  • "cannot read trash config from .obsidian/app.json" — the config file exists but is unreadable (permissions, corruption); the delete is blocked to prevent accidental permanent deletion when the user may have configured .trash/ retention

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

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesVault-relative path of the note to delete, including the ".md" extension
prune_empty_foldersNoWhen 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.

TDQS

A4.3/5.0
Behavior5/5

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

Deeply discloses behavior beyond annotations: trashOption semantics, retention windows, Sync bypass, protected paths, best-effort pruning, and a full error catalog with recovery guidance. This is exceptional transparency for a destructive operation.

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

Conciseness3/5

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

Front-loaded with the core action, but the description is unusually long and dense, with extensive error enumeration and edge-case narration that could overwhelm an agent. Structure is logical but not concise.

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

Completeness5/5

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

Given the tool's destructive nature, no output schema, and complex vault settings, the description covers outcome meanings, error recovery, and side effects (broken links) thoroughly.

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

Parameters3/5

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

Schema description coverage is 100%, so both parameters are already documented. The description adds examples (prune_empty_folders: true) but no semantics beyond what the schema provides. Baseline 3 applies.

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

Purpose5/5

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

States a specific verb+resource (delete a markdown note) and immediately differentiates from siblings by naming vault_delete_memory as the right tool for memory entries. The distinction is unambiguous.

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

Usage Guidelines4/5

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

Provides a 'When to use' section and an explicit alternative (vault_delete_memory for About Me/ entries), plus protected-path error routing. No explicit when-not-to-delete beyond the memory alternative, but the routing is clear.

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

vault_delete_spanDelete SpanA
Destructive

Delete a contiguous block of whole lines from a note's body by referencing short anchor substrings instead of reproducing the full block text. Each anchor locates a full line — the entire line is selected, not just the matching substring. Case-sensitive matching. Properties are preserved; YAML formatting may be normalized to block style on first edit. Operates on the body only.

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

When to use: Removing a block you have already read — a table row, callout, or run of list items — where reproducing it exactly as old_text would be error-prone. Pick a short, unique fragment of the first line for start_anchor and, for a multi-line block, the last line for end_anchor. Prefer vault_replace_in_note for small in-place edits (this tool only deletes). To replace a block, prefer vault_replace_span (one atomic step); otherwise delete it here, then vault_patch_note to add the new content.

Parameters:

  • start_anchor + end_anchor define a line range, not a text range — each anchor locates a full line, and entire lines are removed (never cuts mid-line). Omit end_anchor for a single-line delete.

  • end_anchor is searched at or after the start line, so the span can never run backward. If both match the same line, only that one line is deleted.

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

  • Blank-line runs left by the deletion are collapsed to a single blank line.

Errors:

  • "note not found" — verify path with vault_list_notes

  • "anchor not found" — fragment not on any line; verify with vault_read_note

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

  • "absolute path blocked" — the path starts at the filesystem root; use a vault-relative path

  • "hidden path blocked" — the path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; hidden paths are not editable, matching Obsidian

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

Returns: Confirmation with lines removed and a truncated preview of the deleted text.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "Tracker.md", "Notes/Plan.md")
end_anchorNoShort, 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.
first_matchNoIf an anchor matches more than one line, delete using the first match instead of erroring (default: false — ambiguity is an error).
start_anchorYesShort, unique substring that identifies the first line of the block (case-sensitive). The entire line is selected, not just the substring. Pick a brief fragment — do not paste the whole block.

TDQS

A4.9/5.0
Behavior5/5

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

Annotations declare destructiveHint=true and readOnlyHint=false, and the description goes well beyond that: properties are preserved, YAML may be normalized to block style on first edit, and the body is the only affected scope. It also lists the full error taxonomy (ambiguous anchors, hidden path blocked, absolute path blocked, concurrent write in progress) with recovery steps — richer behavior than annotations alone.

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

Conciseness4/5

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

Front-loaded with purpose and mechanism, then examples, when-to-use, parameters, errors, and returns in a scannable structure. Slightly long for a single tool, but every section earns its place; a minor deduction only for density that could have trimmed one of the two examples.

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

Completeness5/5

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

No output schema exists, yet the description specifies the return shape (confirmation with lines removed and a truncated preview). Combined with the error catalog, sibling routing, and parameter semantics, an agent has everything needed to call and recover from this destructive tool. Complexity is high but the description matches it.

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

Parameters5/5

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

Schema coverage is 100%, so baseline is 3, but the description adds real value the schema cannot: anchors define a line range rather than a text range (never cuts mid-line), end_anchor is searched at or after the start line so the span cannot run backward, same-line match deletes one line, first_match applies independently per anchor, and blank-line runs collapse. These are semantics, not restatements.

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

Purpose5/5

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

States a specific verb (delete) and resource (contiguous block of whole lines from a note's body), and immediately defines the anchor-based mechanism. It explicitly distinguishes itself from siblings: vault_replace_in_note, vault_replace_span, and vault_patch_note are all named with their roles, so an agent can route correctly without opening other schemas.

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

Usage Guidelines5/5

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

Has an explicit 'When to use' section naming the scenario (removing a block you already read where reproducing old_text would be error-prone), and routes to alternatives with conditions: vault_replace_in_note for small edits, vault_replace_span for atomic block replacement, then vault_patch_note to add content after deletion. This is textbook when/when-not/alternatives guidance.

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

vault_find_orphansFind OrphansA
Read-onlyIdempotent

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

Example: vault_find_orphans({ exclude_folders: ["Daily Notes","Templates","About Me"] })

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 (["Daily Notes","Templates","About Me"]), it does not add to them — include the defaults yourself to keep them. Matched by folder prefix, recursing into subfolders ("Projects" also excludes "Projects/Archive").

  • limit (default 50) caps results after sorting by most-recently-modified.

Errors:

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

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

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax results (default 50)
exclude_foldersNoFolders to exclude — replaces the defaults (["Daily Notes","Templates","About Me"]), not merged

TDQS

A5/5.0
Behavior5/5

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

Annotations already declare readOnlyHint, idempotentHint, and non-destructive behavior, so the description adds valuable context beyond them: self-links are ignored, exclude_folders is matched by prefix and recurses, empty results are a valid outcome, and results are sorted by most-recently-modified. This gives the agent an accurate behavioral model without contradiction.

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

Conciseness5/5

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

The description is well-organized and front-loaded: it opens with the core definition, then provides an example, usage guidance, parameter details, error semantics, and return shape. Every section earns its place, and bullet-style formatting makes the information easy to scan.

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

Completeness5/5

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

Given that there is no output schema, the description fully compensates by listing the returned fields, sorting behavior, and the empty-array meaning. It also covers edge cases (self-links, folder prefix recursion) and gives actionable follow-up guidance with vault_patch_note. Nothing needed to invoke the tool correctly is missing.

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

Parameters5/5

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

Although the schema already describes both parameters, the description adds significant meaning: exclude_folders replaces rather than augments the defaults, includes the exact default list, explains prefix matching and recursion, and clarifies that limit caps after sorting. This goes well beyond the schema's short descriptions.

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

Purpose5/5

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

The description uses a specific verb+resource: 'Find notes with no incoming links from other notes,' and further defines what counts as an orphan (self-links ignored). It is clearly distinguishable from siblings, especially vault_get_backlinks, by scoping to graph-wide orphan detection rather than single-note connectivity.

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

Usage Guidelines5/5

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

The description explicitly states when to use the tool ('Vault maintenance — surfacing notes to integrate into the graph') and names the alternative for the one-note case ('Prefer vault_get_backlinks to check the connectivity of one specific note'). This leaves no ambiguity about tool selection.

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

vault_get_daily_noteGet Daily NoteA
Read-onlyIdempotent

Read a daily note by date, using the vault's configured Daily Notes folder and filename date format. Each setting comes from the DAILY_NOTES_FOLDER / DAILY_NOTES_FORMAT env vars, falling back to the vault's .obsidian/daily-notes.json, then to "Daily Notes" and YYYY-MM-DD. Defaults to today if no date is provided.

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. Handles path resolution automatically using the vault's Obsidian config — you don't need to know the folder name or filename format. To append content to a daily note section, use the returned path with vault_patch_note. Use vault_recent_notes to review recent vault activity around a date (not date-filtered — returns globally recent notes).

Parameters:

  • date is ISO YYYY-MM-DD (e.g. "2026-05-13"). Defaults to today in the server's local timezone. Past and future dates are both valid — the tool resolves the configured path for any date and reports exists: false if the note hasn't been created yet. The path is derived from the server's daily-notes settings, so callers never need to construct daily note paths manually.

Errors:

  • "invalid date" — use YYYY-MM-DD format (e.g. "2026-05-13", not "May 13")

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

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

ParametersJSON Schema
NameRequiredDescriptionDefault
dateNoYYYY-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.

TDQS

A4.9/5.0
Behavior5/5

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

Annotations already declare readOnly, idempotent, and non-destructive. The description adds behavioral details beyond annotations: returns exists:false if note missing, resolves path automatically, explains timezone handling, and lists error conditions. No contradictions.

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

Conciseness4/5

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

The description is well-structured with clear sections, but contains redundancy: the error text and 'exists: false' behavior are repeated. Could be tightened without losing clarity, but is still acceptable.

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

Completeness5/5

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

No output schema exists, yet the description fully explains the return JSON structure (path, content, exists), default behavior, error cases, and interaction with other tools. It is complete for the tool's function.

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

Parameters5/5

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

Schema coverage is 100% but the description enriches the date parameter by explaining default to today, server timezone usage, invalid format error, and that the path is derived automatically. This adds meaning beyond the schema.

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

Purpose5/5

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

The description clearly states 'Read a daily note by date' with a specific verb and resource, and distinguishes itself from siblings like vault_read_note by specifying it uses the daily notes folder and format. It is unambiguous and specific.

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

Usage Guidelines5/5

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

Explicitly states when to use ('When you need today's or a specific date's daily note') and contrasts with vault_recent_notes ('not date-filtered'). Also suggests using vault_patch_note for appending, providing clear guidance on alternatives.

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

vault_get_memoryGet MemoryA
Read-onlyIdempotent

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

Example: vault_get_memory({ file: "Principles", section: "Decision heuristics (newest first)" })

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.

Errors:

  • "section requires a file" — section was provided without file; pass both or just file

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

  • "memory file must not start with a dot" — a dot-prefixed name would be a hidden file; memory files are always visible notes

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

Returns: Raw markdown text.

ParametersJSON Schema
NameRequiredDescriptionDefault
fileNoMemory file name without .md (e.g. "Principles", "Opinions")
sectionNoH2 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.

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is clear. The description adds behavioral context beyond annotations: it notes that returning no args yields all files concatenated ('can be large'), returns empty string when no memory files exist, and details each error case. The only gap is not explaining the exact return format (raw markdown is mentioned) or pagination, but that's minor given the tool's simplicity.

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

Conciseness4/5

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

The description is well-structured with clear sections: mode behavior, example, when to use, error list, return type. Every sentence provides useful information. Minor inefficiency: repeating 'call vault_list_memory_files first' in both the guidelines and the schema description could be consolidated, but overall it's concise.

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

Completeness4/5

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

The description is complete for a read-only tool with two optional parameters. It covers all input modes, error cases, appropriate prerequisites (list files first), and return type. With no output schema, the description adequately explains return values (raw markdown, empty string). The only minor gap is not explaining how large concatenated output might be limited or truncated, but that's acceptable for this use case.

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

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds significant value by explaining how the parameter combos affect output (file, file+section, no args) and giving a concrete example. It also clarifies the section matching behavior (case-insensitive, optional suffix). However, it doesn't explain the exact format of the section heading beyond saying 'H2 heading', which is already clear from the example.

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

Purpose5/5

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

The description clearly states that the tool reads semantic memory from 'About Me/' files and describes three modes of operation (file, file+section, no args). It distinguishes itself from sibling tools like vault_read_note, vault_memory_recall, and vault_get_daily_note by focusing specifically on structured memory files with bullet entries.

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

Usage Guidelines5/5

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

The description explicitly tells the agent when to use this tool (reading user preferences, principles, opinions) and when not to ('Prefer vault_read_note for reading non-memory notes'). It also advises calling vault_list_memory_files first to discover valid file and section names, which is a valuable usage guideline.

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

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

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

Parameters:

  • anchor locates a full line — the content is inserted as whole lines before or after it (never splits a line).

  • position: "before" inserts above the anchor line; "after" inserts below it.

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

  • first_match: when the anchor matches multiple lines, takes the first instead of erroring.

Errors:

  • "note not found" — verify path with vault_list_notes

  • "anchor not found" — fragment not on any line; verify with vault_read_note

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

  • "absolute path blocked" — the path starts at the filesystem root; use a vault-relative path

  • "hidden path blocked" — the path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; hidden paths are not editable, matching Obsidian

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

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

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

Returns: Confirmation message "Inserted lines <before|after> anchor in " — N counts the lines content supplied.

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

TDQS

A4.8/5.0
Behavior5/5

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

Annotations cover only the safety profile (readOnly=false, destructive=false), and the description goes well beyond: side effects (YAML normalized to block style on first edit), case-sensitive matching, ambiguity-is-error behavior, concurrency conflicts, hidden/absolute path blocking, control-character rejection, and control-character-free content requirement. This is rich, actionable behavioral disclosure.

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

Conciseness4/5

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

Front-loaded with purpose, then two worked examples, then conditions and errors. The two examples and the error table are high-value and earn their space. Minor redundancy exists where the 'Parameters:' section restates schema descriptions verbatim, and the error list is longer than strictly needed.

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

Completeness5/5

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

With no output schema, the description still supplies the return format ('Inserted <N> lines <before|after> anchor in <path>'), full error enumeration with recovery hints, and Obsidian syntax caveats. 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.

Parameters4/5

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

Schema coverage is 100%, so much of the 'Parameters:' section restates the schema. However, the description adds genuine meaning beyond it: the anchor targets a full line and insertion never splits a line, that blank lines/trailing newline have specific effects, and the first_match disambiguation behavior ties to the ambiguity error.

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

Purpose5/5

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

States a specific verb+resource+mechanism: inserts whole lines before/after a line located by an anchor substring. Explicitly distinguishes itself from siblings (vault_patch_note for heading-targeted inserts, vault_replace_span for replacements, vault_delete_span for shared anchor semantics), so an agent can differentiate it without opening other schemas.

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

Usage Guidelines5/5

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

A dedicated 'When to use' section gives concrete scenarios (table rows, list items, landmarks) and names two alternatives with the conditions that select them. Explicit routing guidance, not inference.

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

vault_list_filesList FilesA
Read-onlyIdempotent

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

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

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

Parameters:

  • folder: folder path filter (e.g. "attachments" or "Projects/media"), searched recursively; omit for the whole vault

  • extensions: restrict to these extensions — case-insensitive, with or without the leading dot (".png" and "png" both work)

  • limit: maximum entries returned (default 50). extension_counts and total always reflect the full filtered set, not just the returned page.

Errors:

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

  • A folder path escaping the vault (e.g. "../elsewhere") is rejected with a path-traversal error.

  • "hidden path blocked" — the folder is hidden (dot-prefixed, like ".obsidian"); hidden folders are not listable, matching Obsidian.

Returns: JSON with files (array of { path, extension, bytes }, sorted by path), extension_counts (per-extension totals over the full filtered set), total (full filtered count), and truncated (true when total exceeds limit). bytes is the on-disk file size, not the delivery cost: reading an image via vault_read_file returns a copy shrunk to fit when needed, so a large listed image is still cheap to read. Text formats return verbatim, so their listed size is what a read delivers. Files of supported types are readable via vault_read_file; vault_search covers markdown notes.

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

TDQS

A4.9/5.0
Behavior5/5

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

Annotations declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, but the description goes far beyond these basics. It explicitly explains error handling for empty/nonexistent folders, path-traversal rejection, and hidden-folder blocking, which is critical for correct agent behavior. It also clarifies that the 'bytes' field is on-disk size, not delivery cost, and explains the nuance that reading an image may return a shrunk copy while text returns verbatim—this is deep transparency about side effects and interpretation.

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

Conciseness4/5

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

The description is quite detailed and well-structured, with clear sections: an intro, examples, when-to-use, parameters, errors, and returns. However, it is longer than necessary and includes some redundancy (e.g., the returns section repeats the path-traversal and hidden-folder points already in the errors section). Nonetheless, the information is densely valuable and front-loaded with the primary purpose and examples, so it earns a high score.

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

Completeness5/5

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

For a list tool with 3 optional parameters and no output schema, the description covers everything needed for correct invocation: parameter semantics, error scenarios, return format (files array, extension_counts, total, truncated), and cross-tool integration (vault_read_file). The agent can call this tool accurately and interpret results without further documentation. Given the richness of the description and the simplicity of the tool, it is thoroughly complete.

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

Parameters5/5

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

Schema coverage is 100%, but the description adds significant semantic value. For folder, it mentions 'searched recursively' and gives concrete examples. For extensions, it confirms case-insensitivity and leading-dot flexibility, matching the schema but adding practical clarity. For limit, it clarifies that extension_counts and total reflect the full filtered set even if limit truncates the returned list—this is critical logic that the schema's 'Max entries returned' alone does not convey.

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

Purpose5/5

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

The description starts with a clear and specific verb and resource: 'List non-markdown files in the vault or a folder'—it immediately conveys what the tool does and its scope. It also lists example file types (images, canvases, PDFs, data files) and subtly contrasts with sibling tools that only cover markdown notes, which distinguishes it from vault_list_notes, vault_search, and vault_search_by_folder.

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

Usage Guidelines5/5

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

The 'When to use' section explicitly states discovery use cases for non-markdown files and references sibling tools (vault_search, vault_list_notes, vault_search_by_folder) to clarify what it does not cover. It also names a specific alternative for a different use case: 'For the files one specific note links to, prefer vault_get_outgoing_links.' This is exemplary routing guidance.

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

vault_list_memory_filesList Memory FilesA
Read-onlyIdempotent

Discovery tool — lists About Me/ memory files with their H1/H2 heading structure, per-section entry counts, entry policy, and each file's leading callout (by convention a "Scope of this file" block describing what belongs in it). Does NOT return actual entries.

Example: vault_list_memory_files() returns file outlines with headings like "Decision heuristics (newest first)", entry counts, each file's entry policy, and its scope callout.

When to use: Discovering what memory files and sections exist — and what each file is for — BEFORE calling vault_get_memory, vault_update_memory, or vault_delete_memory. Always call this first to get valid file and section names, and to check a file's entry policy before pruning entries.

Errors:

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

Returns: JSON array of file outlines, each { file, title, bytes, entry_policy, leading_callout, headings } — bytes is the on-disk file size; entry_policy is "append-only" (the default — entries are never edited or deleted) or "living" (a current-state file whose expired entries may be pruned; declared via entry-policy frontmatter); leading_callout is the file's top-of-file callout ({ type, title, body }), by convention a "Scope of this file" block, or null.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.6/5.0
Behavior4/5

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

Annotations already indicate readOnlyHint=true and idempotentHint=true. The description adds value by detailing that an empty or nonexistent memory folder returns an empty array (not an error) and by explaining the return structure including the leading callout and entry_policy fields. No contradictions with annotations.

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

Conciseness4/5

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

The description is well-structured with a clear first sentence stating purpose, followed by an example, usage guidance, error behavior, and return format. It is appropriately detailed without being excessively verbose.

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

Completeness5/5

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

Given the tool has no parameters and no output schema, the description provides a complete picture: it explains what the tool returns (JSON array of file outlines with key fields), how to use it, and error handling. There are no gaps.

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

Parameters4/5

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

There are no parameters, so the baseline is 4 as per the guidelines. The description does not need to provide parameter information and correctly omits it.

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

Purpose5/5

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

The description clearly states it's a discovery tool for listing memory files with their heading structure, entry counts, and entry policy. It explicitly distinguishes its purpose from sibling tools by noting it should be called before vault_get_memory, vault_update_memory, or vault_delete_memory.

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

Usage Guidelines5/5

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

The description provides explicit guidance on when to use the tool: always before getting, updating, or deleting memory files to get valid file and section names and to check entry policy. This effectively differentiates its usage from sibling memory tools.

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

vault_list_notesList NotesA
Read-onlyIdempotent

List .md file paths in the vault, optionally filtered by folder and/or glob pattern. Returns paths only — not content or metadata.

Example: vault_list_notes({ folder: "Projects" }) Example: vault_list_notes({ glob: "**/session-log.md" })

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 scopes the listing to a path prefix ("Projects" includes "Projects/Archive"). When combined with glob, the glob pattern is applied within the folder's scope.

  • glob supports * (any filename chars) and ** (any path depth). Applied to vault-relative paths.

Errors:

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

  • "absolute path blocked" — the folder starts at the filesystem root; use a vault-relative folder path.

  • "hidden path blocked" — the folder is hidden (dot-prefixed, like ".obsidian"); hidden folders are not listable, matching Obsidian.

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

ParametersJSON Schema
NameRequiredDescriptionDefault
globNoGlob pattern for path filtering (e.g. "**/*session-log*.md"). Supports * and ** wildcards. Combined with folder when both are set.
folderNoFolder path prefix (e.g. "About Me", "Projects"). Includes all subfolders.

TDQS

A4.6/5.0
Behavior4/5

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

Annotations already declare readOnly/idempotent/non-destructive, so the safety profile is covered. The description goes further by disclosing genuinely non-obvious behavior: a nonexistent folder or zero matches returns an empty array rather than an error, and absolute or hidden paths are blocked. That is real added value beyond the structured fields.

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

Conciseness4/5

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

Front-loaded with the one-line purpose, then examples, usage, parameters, errors, and returns in labelled sections so an agent can skim. Slightly verbose with two examples and an enumerated error list, but each block carries distinct information.

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

Completeness5/5

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

Even without an output schema, the description states the return type precisely (JSON array of vault-relative path strings, with examples) and documents the failure modes that matter for calling it correctly. Nothing an agent needs is missing.

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

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema does not: folder is a prefix that pulls in subfolders ("Projects" includes "Projects/Archive"), and when combined with glob the pattern is applied within the folder's scope, using vault-relative paths.

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

Purpose5/5

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

States a specific verb and resource ("List .md file paths in the vault") plus the scope constraint ("optionally filtered by folder and/or glob pattern") and explicitly delimits what is returned ("paths only — not content or metadata"). This lets an agent distinguish it from vault_search_by_folder and vault_search without opening a schema.

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

Usage Guidelines5/5

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

Gives an explicit "When to use" statement and names three alternatives with the conditions that select them: vault_search_by_folder for metadata, vault_search for content, vault_read_note to read a result. 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_keysList Property KeysA
Read-onlyIdempotent

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

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

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 is matched as a path prefix and recurses into subfolders ("Projects" also covers "Projects/Archive"); omit it to scan the entire vault.

Returns: JSON array of { key, count, sample_values } sorted by count descending. sample_values shows the top 3 most common values per key for quick orientation.

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

TDQS

A4.7/5.0
Behavior4/5

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

Annotations already declare readOnlyHint and idempotentHint; the description adds behavioral details like sorted output and sample_values top 3, plus folder recursion behavior. No contradiction.

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

Conciseness5/5

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

Well-structured: purpose with example, usage guidance, parameter explanation, return format. Front-loaded with key information, no fluff.

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

Completeness5/5

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

Given the tool's simplicity (1 param, no output schema), the description fully covers parameter behavior, output format, usage context, and sibling differentiation. Complete and actionable.

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

Parameters4/5

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

Schema description for 'folder' is minimal; the description adds crucial behavior: path prefix matching and recursion into subfolders. Schema coverage is 100%, but description adds extra value.

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

Purpose5/5

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

The description clearly states the tool discovers all property keys with note counts and sample values, and distinguishes it from siblings like vault_list_property_values and vault_search_by_property.

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

Usage Guidelines5/5

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

Explicitly states when to use as a first step for vault orientation, and provides alternatives: prefer vault_list_property_values for specific key values, and vault_search_by_property for key-value matching.

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

vault_list_property_valuesList Property ValuesA
Read-onlyIdempotent

List distinct values for a specific property key with note counts. Useful for discovering the range of values a property takes before searching.

Example: vault_list_property_values({ key: "status" }) returns [{ value: "active", count: 47 }, { value: "done", count: 211 }, ...]

When to use: Enumerating possible values for a property key before calling vault_search_by_property. Handles both scalar properties (status: "active") and array properties (tags: ["a", "b"]) — array elements are unpacked and counted individually, so the sum of counts may exceed the note count. An unknown key or empty folder returns an empty array, not an error. Call vault_list_property_keys first to discover valid key names.

Parameters:

  • key is case-sensitive and must match exactly as returned by vault_list_property_keys. Values are always strings — numeric and boolean properties are stringified for counting.

  • folder + key interact: folder restricts counting to a subtree, so the same key can return different value distributions depending on folder scope.

  • limit (default 50) applies after sorting by count descending, so you always get the most-used values first. Increase for high-cardinality keys like "title" or "created".

Returns: JSON array of { value, count } sorted by count descending.

ParametersJSON Schema
NameRequiredDescriptionDefault
keyYesProperty key name — use vault_list_property_keys to discover valid keys (e.g. "status", "type", "tags").
limitNoMax values to return (default 50). Increase for high-cardinality properties.
folderNoRestrict to a folder prefix (e.g. "Projects")

TDQS

A4.7/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so the description need not repeat those. It adds valuable behavioral details such as array elements being unpacked and counted individually, sum of counts possibly exceeding note count, and unknown keys returning an empty array rather than an error. It also mentions sorting behavior. This is rich behavioral context beyond the annotations.

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

Conciseness5/5

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

The description is well-structured with sections for use cases, parameters, and returns. It is concise but packed with essential information, using examples and bullet points to enhance readability. No wasted words; every sentence adds value.

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

Completeness5/5

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

The description is complete for a read-only, list-style tool. It covers what it does, when to use it, edge cases (unknown key, empty folder), parameter semantics, and return format. With high schema coverage and read-only annotations, nothing important is missing. It even provides a concrete example. For its complexity level, this definition is thorough.

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

Parameters4/5

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

Schema description coverage is 100%, so the schema already documents all parameters. The description adds critical semantics: case-sensitivity of the key, stringification of values, the interaction between folder and key affecting value distributions, and how limit interacts with sorting (applies after sorting by count descending). This exceeds the baseline without being redundant.

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

Purpose5/5

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

The description states a specific verb ('List') and resource ('distinct values for a specific property key'), clarifying its purpose with an example. It is clearly distinguished from siblings like vault_search_by_property, which searches notes, and vault_list_property_keys, which lists keys, by focusing on values and counts.

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

Usage Guidelines5/5

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

Explicit 'When to use' section provides clear guidance: use before vault_search_by_property to enumerate possible values, and call vault_list_property_keys first to discover valid keys. Also specifies that it handles both scalar and array propertiestools, and notes when results may exceed note count. This is exemplary guidance for selecting the tool.

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

vault_list_tagsList TagsA
Read-onlyIdempotent

List all tags in the vault with note counts, ordered by count descending. Only frontmatter tags are counted (inline #tags in note bodies are not indexed). Each hierarchical tag (e.g. "project/vault-cortex") appears as one full entry, not split into segments. Count is unique notes, not occurrences. A vault with no tagged notes returns an empty array.

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

When to use: Discovering what tags exist before searching by tag. Good first step for vault orientation. Prefer vault_search_by_tag once you know which tag to query — it supports hierarchical prefix matching ("project" matches "project/*").

Returns: JSON array of { tag, count } sorted by count descending. tag omits the "#" prefix; count is unique notes with this tag.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.9/5.0
Behavior5/5

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

Annotations already declare readOnlyHint and idempotentHint. The description adds critical behavioral details: only frontmatter tags are counted (not inline #tags), hierarchical tags are kept intact, count is unique notes, and empty vault returns empty array. No contradiction with annotations.

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

Conciseness5/5

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

Description is concisely structured: core purpose first, then important counting rules, example, usage guidance, and return format. Every sentence adds value without redundancy.

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

Completeness5/5

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

Despite no parameters and no output schema, the description fully explains behavior, edge cases (empty array), and return format (JSON array of { tag, count }). Combined with annotations, the agent has all necessary context for correct invocation.

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

Parameters4/5

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

Tool has zero parameters, so the input schema is fully covered. The description adds no parameter information (unnecessary), which is appropriate. Baseline of 4 applies per guidelines.

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

Purpose5/5

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

The description clearly states the verb ('List') and resource ('all tags in the vault'), specifies ordering and note counts, and differentiates from the sibling vault_search_by_tag by noting it is a discovery step versus a targeted search.

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

Usage Guidelines5/5

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

Provides explicit when-to-use ('Discovering what tags exist before searching by tag') and when-not-to-use ('Prefer vault_search_by_tag once you know which tag to query'), including a clear alternative tool.

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

vault_list_tasksList TasksA
Read-onlyIdempotent

List checkbox tasks across the whole vault with structured filters — the Tasks-plugin data model over MCP. Both task metadata formats are indexed: emoji signifiers (📅 due, ⏳ scheduled, 🛫 start, ➕ created, ✅ done, ❌ cancelled, 🔺⏫🔼🔽⏬ priority, 🔁 recurrence, 🏁 onCompletion, 🆔/⛔ dependencies) and Dataview inline fields ([due:: 2026-07-04], [priority:: high], ...). Every result carries its attribution — note path, folder, line number, and the nearest heading when the task sits under one (the lane on a Kanban board) — so no follow-up reads are needed to locate a task. Task lines inside fenced code blocks and %% %% comment blocks are not indexed.

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; position is the natural sort for boards (file path then line number, preserving card arrangement) Example: vault_list_tasks({ folder: "Code Projects/vault-cortex" }) — all open tasks across a project tree (TASKS.md + task-notes/ subdirectories); folder is a recursive prefix match Example: vault_list_tasks({ status: "done", done: { after: "2026-06-26" } }) — what got completed this week Example: vault_list_tasks({ top_level_only: true, path: "TASKS.md" }) — board cards only, excluding checklist sub-items

When to use: 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) to read one specific board lane verbatim. Prefer vault_search for full-text queries over note content.

Parameters:

  • status: a single value or an array of values, OR-combined (default "not_done"). Values: "not_done" (todo + in_progress, excludes done AND cancelled), "todo", "in_progress", "done", "cancelled", "all". Virtual values expand in arrays: ["not_done", "done"] matches todo + in_progress + done.

  • due / scheduled / start / done / created / cancelled: date filters, each { before, on, after } in YYYY-MM-DD — before/after are exclusive, on is exact. A date filter only matches tasks that HAVE that date.

  • priority: array of "highest" | "high" | "medium" | "low" | "lowest" | "none", OR-combined ("none" = tasks with no priority signifier).

  • folder: recursive note-path prefix. tag: bare inline-task-tag name; a parent tag matches children. heading: exact heading text or array of headings, case-sensitive, OR-combined. path: one note, must end in ".md".

  • top_level_only: boolean (default false). When true, only top-level tasks (depth 0) are returned — excludes indented sub-tasks and checklist items.

  • sort_by: "due" (default) | "scheduled" | "start" | "created" | "done" | "priority" | "note_mtime" | "position". "position" sorts by file path then line number — the natural order for Kanban boards.

  • limit: max results (default 50). The total field always reports the full match count.

Errors:

  • A malformed or calendar-invalid date filter throws with remediation text ("Use YYYY-MM-DD")

  • path without the ".md" extension is rejected

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

Returns: JSON { total, tasks }. Every task carries path, line, status, status_char, description, folder, depth (0 for top-level, 1+ for sub-tasks), is_kanban_task, depends_on, and tags (the arrays are [] when empty). Every other field appears only when the task has it: heading (nearest heading above the task), created/scheduled/start/due/done/cancelled dates, priority, recurrence, on_completion, task_id, block_id, parent_block_id (sub-tasks whose parent carries a ^block-id), done_lanes (Kanban boards only), and subtask_progress — { done, total } over the task's DIRECT checklist children, present only when the task has a checklist (absent = no checklist items); 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
dueNoDue date (📅 / [due:: ]) bounds
tagNoInline task tag, bare name without "#"; parent tags match children
doneNoDone date (✅ / [completion:: ]) bounds
pathNoRestrict to one note (vault-relative path ending ".md")
limitNoMax results (default 50); total always reports the full match count
startNoStart date (🛫 / [start:: ]) bounds
folderNoRestrict to a note-path prefix (e.g. "Code Projects/vault-cortex")
statusNoStatus 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.not_done
createdNoCreated date (➕ / [created:: ]) bounds
headingNoExact heading text or array of headings, OR-combined, case-sensitive (e.g. "Active" or ["Active", "Up Next"])
sort_byNoSort key (default "due"). Date sorts cascade through related fields when the primary is absent; each fallback uses its own natural direction. "position" sorts by file path then line number — the natural order for Kanban boards.due
priorityNoPriority levels, OR-combined; "none" selects tasks with no priority signifier
cancelledNoCancelled date (❌ / [cancelled:: ]) bounds
scheduledNoScheduled date (⏳ / [scheduled:: ]) bounds
sort_directionNoSort direction. Default per field: "asc" for due/scheduled/priority/position, "desc" for start/created/done/note_mtime. Within a date cascade, each fallback uses its own default; an explicit value overrides all fields uniformly.
top_level_onlyNoWhen true, only top-level tasks (depth 0) are returned — excludes indented sub-tasks and checklist items. Default false.

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds substantial behavioral context beyond that: both emoji and Dataview formats are indexed, fenced code blocks and comment blocks are excluded, results carry attribution, limit still reports full totals, and subtask_progress has precise counting semantics. No contradiction with annotations.

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

Conciseness4/5

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

The description is long, but the tool has 16 parameters and no output schema, so length is largely justified. It is well-structured with 'When to use', 'Errors', 'Returns', and illustrative examples. It earns a 4 rather than 5 because some parameter text closely duplicates the schema descriptions and the example list could be trimmed without loss.

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

Completeness5/5

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

With no output schema, the description carries full responsibility for return values, and it delivers: it enumerates every task field, distinguishes always-present vs conditional fields, explains subtask_progress counting, covers error behavior, defines no-match behavior, and clarifies default status and sort. Nothing an agent needs to call this tool correctly appears to be missing.

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

Parameters4/5

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

Schema coverage is 100% and each parameter already has a solid schema description, so the baseline is 3. The description adds meaningful extra semantics: virtual status expansion in arrays, exclusive before/after date behavior, folder as recursive prefix, case-sensitive exact heading match, '.md' requirement on path, and the meaning of 'position' sort. This exceeds the schema but does not fully reinvent it; hence 4 rather than 5.

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

Purpose5/5

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

The description opens with a specific verb, resource, and scope: 'List checkbox tasks across the whole vault with structured filters'. It also names the data model (Tasks-plugin) and later contrasts itself with vault_read_note and vault_search, so an agent can immediately distinguish this search/list tool from siblings.

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

Usage Guidelines5/5

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

The 'When to use' section gives concrete question types ('what's overdue?', 'what's open per project?'), and it explicitly says to prefer vault_read_note for reading one board lane verbatim and vault_search for full-text queries. This is explicit routing guidance with exclusions, leaving little to inference.

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

vault_memory_recallMemory RecallA
Read-onlyIdempotent

Recall memory entries about a topic — entry-granular hybrid (keyword + semantic) retrieval across ALL About Me/ files and ALL time. Returns every relevant dated entry sorted oldest-first, so the full evolution of a preference, opinion, or fact is visible — semantic matching finds early entries even when their phrasing differs from the query. Tuned for recall over precision: expect some marginal entries and judge relevance yourself when synthesizing an answer. Content-word queries ("testing philosophy", "sustainable pacing") rank best; a meta-framed query ("opinions on testing") whose relevance cut would come back empty degrades to relaxed any-term keyword matching instead of returning nothing.

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

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

Errors:

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

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

Returns: JSON { entries, total, truncated, search_mode, reranked }. Each entry is { file, section, date, text } — text is the raw entry markdown (wikilinks intact, continuation lines included); file and section feed directly into vault_get_memory or vault_delete_memory. entries ascend by date (oldest first). total counts all matched entries; truncated=true means limit dropped the least-relevant matches — never a date range — so raise limit or narrow the query for the complete set. search_mode is "hybrid" when vector matching contributed, "fts" when the entries came from keyword matching alone — including the any-term fallback that rescues a would-be-empty result; reranked is true when the cross-encoder relevance cut was applied.

ParametersJSON Schema
NameRequiredDescriptionDefault
fileNoOptional: restrict to one memory file, name without .md (e.g. "Opinions"). Omit for cross-file recall — the default and usual choice.
limitNoCap on returned entries (default 50). When more match, the least-relevant are dropped and truncated=true — never a date range.
queryYesTopic 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")

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false, so the safety profile is covered. The description adds substantial behavioral context beyond that: the recall-over-precision tuning, the fallback to relaxed any-term keyword matching when a meta-framed query would return empty, the oldest-first date ordering, the meaning of truncated (drops least-relevant matches, never a date range), and the search_mode/reranked fields. This is rich, non-obvious behavior that an agent needs to interpret results correctly.

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

Conciseness4/5

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

The description is long but every section earns its place: purpose, query guidance, examples, when-to-use, error behavior, and return semantics. It is front-loaded with the core purpose and scoping, then moves to usage guidance and edge cases. It could be slightly tightened, but the density of useful information justifies the length.

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

Completeness5/5

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

For a retrieval tool with no output schema, the description fully compensates by documenting the return shape ({ entries, total, truncated, search_mode, reranked }), the entry fields ({ file, section, date, text }), the ordering, the truncation semantics, and the error behavior (empty result, not an error). It also covers the main failure mode (unknown file) and how to recover. Nothing an agent needs to call it correctly and interpret results is missing.

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

Parameters4/5

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

Schema description coverage is 100%, so the schema already documents all three parameters. The description adds value by explaining the query semantics in depth (content words vs meta framing, semantic matching bridging phrasing drift) and by clarifying the file parameter's default behavior (omit for cross-file recall). The limit parameter's truncation behavior is also explained in the description. This goes beyond the schema's baseline, though the schema already carries the core meaning.

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

Purpose5/5

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

The description states a specific verb ('Recall'), a specific resource ('memory entries about a topic'), and the retrieval mechanism ('entry-granular hybrid keyword + semantic retrieval across ALL About Me/ files and ALL time'). It clearly distinguishes itself from siblings like vault_get_memory and vault_search by naming them and the conditions for preferring them. The example calls further anchor the tool's purpose.

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

Usage Guidelines5/5

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

The description explicitly says when to use this tool ('Answering what does my memory say about X? or how has my view on Y evolved?') and when not to ('Prefer vault_get_memory to read a known file or section verbatim; prefer vault_search for notes outside the memory layer'). It also provides error-handling guidance (unknown file returns empty results, call vault_list_memory_files) and query-shaping advice (content-word queries rank best).

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

vault_move_noteMove NoteA
Destructive

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

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

When to use: Renaming a note or relocating it to a different folder while keeping the link graph intact. Prefer this over vault_write_note + vault_delete_note, which would orphan every backlink. To only change a note's body or properties, use vault_patch_note or vault_update_properties. Protected paths (About Me/ and the daily notes folder (read from DAILY_NOTES_FOLDER or .obsidian/daily-notes.json, defaulting to Daily Notes/)) cannot be moved.

Errors:

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

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

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

  • "path must end in …" — old_path or new_path is missing the .md extension; both paths must end in .md.

  • "absolute path blocked" — old_path or new_path starts at the filesystem root; use vault-relative paths.

  • "path traversal blocked" — a path escapes the vault root; use vault-relative paths.

  • "hidden path blocked" — old_path or new_path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; notes cannot be moved from or into hidden paths, matching Obsidian.

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

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

  • Mid-move I/O failure (rare, e.g. a permission or disk error while writing) — no failure loses data, and the error message names what failed and the resulting state. An ordinary move deletes the original only after the destination and all backlinks are written: if a backlink write failed, new_path exists and the original is intact (re-run the move, deleting the partial new_path first, to finish); if the final delete failed, both old_path and new_path exist (delete old_path to finish). A case-only rename commits by renaming in place instead: if the rename itself failed, nothing was written; if a later write failed, the note already lives at new_path — fix the remaining links in place (the error names them) rather than re-running the move, whose old_path no longer exists.

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

Returns: JSON with moved_to (the new path), links_updated (count of link occurrences rewritten), updated_notes (sorted paths of the other notes that were edited; the moved note is implied by moved_to), and pruned_empty_folders (count of source folders removed — 0 unless prune_empty_folders was set).

ParametersJSON Schema
NameRequiredDescriptionDefault
new_pathYesDestination vault-relative path (e.g. "Projects/Spec.md"). Must end in .md and must not already exist; parent folders are created as needed.
old_pathYesCurrent vault-relative path of the note to move (e.g. "Inbox/Draft.md"). Must end in .md.
prune_empty_foldersNoWhen 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.

TDQS

A4.4/5.0
Behavior3/5

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

Annotations already declare destructiveHint=true, openWorldHint=false, idempotentHint=false, so the safety profile is known; the description nonetheless goes well beyond them with an error catalog, atomic commit ordering, partial-failure recovery states, concurrency locking, and protected-path rules. Under the rubric the annotation bar is lower but this adds substantial genuinely-behavioral context, so a 3 is conservative rather than generous.

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

Conciseness4/5

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

Long but organized into labeled blocks (examples, when-to-use, errors, syntax rules, returns) with the core purpose front-loaded. Some error-case prose is dense, but nearly every sentence carries actionable information for a complex, failure-sensitive operation.

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

Completeness5/5

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

With no output schema, the description fully documents the return fields (moved_to, links_updated, updated_notes, pruned_empty_folders) and covers the edge cases an agent needs — protected paths, extension requirement, no-overwrite, and per-error recovery — so nothing required 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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3; the description still adds meaning beyond the schema by demonstrating each parameter in worked examples, notably the case-only rename that works where the filesystem treats both spellings as one file, and by tying prune_empty_folders to concrete outcomes.

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

Purpose5/5

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

States a specific verb+resource ('Move or rename a note') and immediately names the distinctive behavior — rewiring every link across the vault. It explicitly contrasts with vault_write_note + vault_delete_note, vault_patch_note, and vault_update_properties, so an agent can distinguish it from siblings without opening a schema.

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

Usage Guidelines5/5

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

Has an explicit 'When to use' clause (rename or relocate while keeping the link graph intact), names the alternatives it beats and why ('would orphan every backlink'), names the tools to use for other jobs, and states which paths cannot be moved (protected folders).

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

vault_patch_notePatch NoteA
Destructive

Surgical edits to a markdown note — append, prepend, replace, or insert content by heading. Frontmatter values are preserved; YAML formatting may be normalized to block style on first edit.

Example: vault_patch_note({ path: "TASKS.md", operation: "append", heading: "Active", content: "- [ ] New task" })

Cross-section move (e.g. completing a task on a board):

  1. vault_read_note to get current content and verify exact text

  2. vault_patch_note({ path, operation: "append", heading: "Done", content: "- [x] Task text" }) to add at target

  3. vault_replace_in_note({ path, old_text: "- [ ] Task text", new_text: "" }) to remove from source (for a large multi-line block, prefer vault_delete_span); on error, re-read and retry until the source copy is gone Add at the target before deleting from the source — the two writes are not atomic, so this order can briefly duplicate the moved block on a failure but never lose it.

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.

Operations:

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

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

  • replace: replace section body (heading preserved; requires heading; errors if the target has child headings unless include_children is set)

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

Heading-targeted ops keep the matched heading and write content verbatim — don't begin content with the target heading (it's rejected to avoid a duplicate). No separator is added around the content — end it with a newline to leave a blank line after the inserted block.

Limitation: A no-heading prepend inserts at body line 0. If the note has content above its first heading and your content starts with a heading, that content becomes the new section's body. The write still succeeds and the confirmation says so — use insert_before on the first heading to place a section above it instead.

Section boundaries: a section spans from its heading to the next heading of the same or higher level (or EOF). Child headings are included in the parent section. 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

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

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

  • "operation … requires a heading target" — replace and insert_before need a heading

  • "content begins with the heading … which would duplicate it" — content's first line repeats the target heading; omit it (the matched heading is kept automatically)

  • "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" — the path starts at the filesystem root; use a vault-relative path

  • "hidden path blocked" — the path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; hidden paths are not editable, matching Obsidian

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

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

Obsidian syntax: Content is Obsidian Flavored Markdown (no escaping applied). Watch for: #word = tag, [[ = wikilink, %% = comment block. Inserting heading-level content (## New Section) changes the note's structure — future heading-targeted ops may resolve differently. Table rows: send only the data row ("| cell1 | cell2 |"), not the header or separator — duplicating them splits the table.

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

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "TASKS.md", "Projects/plan.md")
contentYesMarkdown 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).
headingNoTarget heading text (case-sensitive exact match). Required for replace and insert_before. Optional for append/prepend (omit for file-level operation).
operationYesappend | prepend | replace | insert_before. replace and insert_before require a heading; append and prepend work with or without one.
heading_levelNoHeading level (1-6) for disambiguation when multiple headings share the same text
include_childrenNoWhen true, allows replace to overwrite a section that contains child headings. Without this, replace errors if children exist — preventing silent data loss.

TDQS

A4.9/5.0
Behavior5/5

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

The description goes far beyond the annotations by disclosing side effects: YAML normalization, section-boundary semantics, non-atomic writes, child-heading destruction risk, duplicate-heading rejection, verbatim content insertion, and control-character rejection. It does not contradict the destructiveHint/readOnlyHint annotations.

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

Conciseness4/5

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

The description is long, but it is well-structured with labeled sections, bullet lists, and an early example. Every section addresses a real usage question or failure mode. A small amount of repetition around heading duplication and newline handling could be trimmed, but the complexity of the tool justifies most of the length.

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

Completeness5/5

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

Given six parameters, no output schema, and a destructive mutation tool, the description is remarkably complete: it covers operations, edge cases, section boundaries, error messages, Obsidian syntax hazards, table-row handling, and the exact return confirmation string. An agent has enough information to invoke the tool correctly and recover from failures.

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

Parameters5/5

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

Schema coverage is 100%, and the description still adds substantial meaning for all parameters: operation-specific requirements for heading, content constraints (no leading target heading, newline guidance), heading_level for disambiguation, and include_children as an explicit confirmation of destructive intent. The worked example also clarifies parameter combination.

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

Purpose5/5

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

The opening sentence names a specific verb ('surgical edits'), a resource ('markdown note'), and the four concrete operations (append, prepend, replace, insert by heading). It clearly differentiates from sibling tools by framing itself as the targeted-edit tool rather than full write or text replacement.

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

Usage Guidelines5/5

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

There is an explicit 'When to use' section that states when to prefer vault_patch_note versus vault_write_note and vault_replace_in_note. It also walks through a cross-section move scenario involving vault_read_note, vault_patch_note, and vault_replace_in_note, with explicit ordering guidance to avoid data loss.

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

vault_read_fileRead FileA
Read-onlyIdempotent

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

Example: vault_read_file({ path: "attachments/diagram.png" }) — the image itself, shrunk to fit response limits when needed Example: vault_read_file({ path: "Boards/Roadmap.canvas" }) — a readable outline of the canvas Example: vault_read_file({ path: "Boards/Roadmap.canvas", raw: true }) — the canvas's exact JSON source Example: vault_read_file({ path: "exports/data.json" }) — the file content as text Example: vault_read_file({ path: "exports/big.csv", limit: 500 }) — the first 500 lines, preceded by a metadata line stating the window and total line count Example: vault_read_file({ path: "papers/research.pdf" }) — structured text with title, headings, and links Example: vault_read_file({ path: "papers/research.pdf", raw: true }) — each page rendered as an image block

What each type returns:

  • Images (.png/.jpg/.jpeg/.gif/.webp): the image as a viewable image block — downscaled and recompressed server-side when it exceeds client response limits, delivered untouched otherwise — plus a text line stating the path, delivered format/dimensions/bytes, and the original dimensions when shrunk. Animated GIFs are reduced to their first frame when recompressed to fit the budget.

  • Canvas (.canvas): a readable markdown outline per JSON Canvas 1.0 — groups (by visual containment), node content in reading order, and a connections list with edge labels. Set raw: true for the exact JSON source instead (geometry, ids, colors — full fidelity).

  • PDFs (.pdf): structured text with document metadata — title, page count, heading hierarchy (from font sizes relative to the body text), code blocks and inline code (from monospace fonts), page separators, and a deduplicated links footer. Richer than flat text extraction: headings, code, and hyperlinks that flat extraction loses are preserved. Set raw: true for page images instead — each page rendered and returned as an image block, showing layout, diagrams, tables, and formatting that text extraction cannot preserve. Image-only and scanned PDFs work in raw mode. Up to 5 pages are rendered.

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

  • Line paging: start_line and limit page any text result — text formats, canvas outlines and raw JSON, PDF-extracted text — as a 1-based line window, preceded by a metadata line ("data.csv — lines 51–100 of 400 (continue with start_line: 101)"). Paged windows come back with \n line endings and no trailing newline; a read without paging inputs stays byte-exact.

When to use: whenever a note references a file you need to actually see or read — an embedded diagram, a linked canvas, data file, or PDF. Find the files a note links to (with byte sizes) via vault_get_outgoing_links; browse a folder's files via vault_list_files. For .md notes use vault_read_note — this tool rejects them. To check a large file's size before reading it whole, request start_line: 1 with limit: 1 — one line plus the total line count.

Errors:

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

  • "file not found" — nothing exists at that path; discover valid paths via vault_list_files

  • "hidden path blocked" — the path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; hidden paths are not readable, matching Obsidian

  • "file too large" — the file exceeds the server's read cap (MAX_FILE_BYTES, default 50 MiB)

  • "text output too large" — a text file or PDF renders past the 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/limit on an image or on a PDF with raw: true; line paging applies to text results only

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

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

  • "PDF page rendering failed" — raw: true was set but no pages could be rendered; the PDF may be corrupt

  • "image cannot be fitted" — the image could not be compressed under the output budget (MAX_IMAGE_OUTPUT_BYTES)

  • "raw source is not available for images" — raw applies to text-representable files; an image's delivered form is its image block

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

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

Search coverage: vault_search indexes markdown notes plus canvas, PDF, and supported text-format content; find other files by browsing (vault_list_files) or through a note's links (vault_get_outgoing_links).

ParametersJSON Schema
NameRequiredDescriptionDefault
rawNoReturn 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.
pathYesVault-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.
limitNoMaximum 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.
start_lineNoFirst 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.

TDQS

A4.4/5.0
Behavior5/5

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

The description goes far beyond the annotations (which already declare idempotentHint=true, readOnlyHint=true). It details how images are downscaled/recompressed, how animated GIFs are handled, how PDFs are extracted (including heading hierarchy and code blocks), line-paging behavior, and the exact format of error messages. This is exceptionally exhaustive for a read-only tool.

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

Conciseness2/5

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

The description is extremely long and contains a lot of detailed behavior, error explanations, and returns information that, while useful for behavioral completeness, could be shortened or moved to tool-specific documentation. The examples at the top are helpful, but the error list in particular is verbose. In its current form, it is not concise.

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

Completeness5/5

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

For a tool with 4 parameters (100% schema description), clear annotations, no output schema, and a complex domain (multiple file types with different behaviors), the description provides exhaustive coverage: per-type return formats, paging details, error messages, search coverage, and relationships to sibling tools. It leaves no question unanswered about what the tool does or how it behaves.

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

Parameters3/5

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. The description restates some parameter semantics (e.g., raw behavior for canvas and PDF, start_line/limit paging) but does not significantly add beyond what the schema descriptions already provide. The examples in the description are valuable context but the parameter schema is already complete and well-documented.

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

Purpose5/5

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

The description is extremely precise: 'Read a non-markdown vault file in its most useful form per type.' It immediately distinguishes itself from vault_read_note (which handles .md files). The description also lists all supported types and their behavior, leaving no ambiguity about the tool's scope.

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

Usage Guidelines5/5

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

The description explicitly states when to use this tool ('whenever a note references a file you need to actually see or read'), when not to use it ('For .md notes use vault_read_note — this tool rejects them'), and how to find files to read (vault_get_outgoing_links, vault_list_files). It also provides guidance on efficient usage ('To check a large file's size... request start_line: 1 with limit: 1').

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

vault_read_noteRead NoteA
Read-onlyIdempotent

Read a markdown note by its vault-relative path. By default returns the full raw content including properties; optional modes return just the properties, just the heading outline, or just one section — so large notes don't blow the token budget.

Example: vault_read_note({ path: "Projects/vault-cortex.md" }) Example: vault_read_note({ path: "Projects/vault-cortex.md", properties_only: true }) Example: vault_read_note({ path: "TASKS.md", outline: true }) Example: vault_read_note({ path: "TASKS.md", heading: "Active" }) Example: vault_read_note({ path: "TASKS.md", heading: "Done", heading_level: 2 }) // disambiguate when several "Done" headings exist Example: vault_read_note({ path: "TASKS.md", heading: "Done", start_line: 1, limit: 20 }) // first 20 lines of an oversized section

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

Errors:

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

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

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

  • "line paging is not available in outline mode" / "... properties_only mode" — start_line/limit only work on text renditions (full read or heading section)

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

  • 'path must end in ".md"' — the path names a non-markdown file; read files (images, .canvas, data files) with vault_read_file instead

  • "absolute path blocked" — the path starts at the filesystem root; use a vault-relative path

  • "hidden path blocked" — the path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; hidden paths are not accessible, matching Obsidian

Returns: Raw markdown string (default); JSON object of properties (properties_only); JSON outline object (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)").

Outline shape: { leading_callout?, leading_content?, headings } — headings is [{ level, text, bytes }]; leading_callout ({ type, title, body }) is the note's top-of-file callout; leading_content is the rest of the body text above the first heading, with the callout's own lines excluded so the two never repeat the same text. Either key is omitted when the note has none. Empty headings ("##" with no text) appear with text: "" — they act as section boundaries but cannot be targeted by the heading parameter; read the parent section (which includes child headings) or the full note, and edit via vault_replace_in_note.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "About Me/Principles.md")
limitNoMaximum lines returned (default: all remaining). A paged read's metadata line states the window, the total line count, and the next start_line.
headingNoReturn only this section (heading line + body, through the next same-or-higher heading). Case-sensitive exact match.
outlineNoIf 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.
start_lineNoFirst 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).
heading_levelNoHeading level (1-6) for disambiguation when multiple headings share the same text; only applies with heading
properties_onlyNoIf true, returns parsed properties as JSON instead of full note content

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds substantial behavioral context beyond that: section boundary semantics, mutual exclusivity of modes, LF normalization for paged reads vs byte-identical unpaged reads, and a complete error catalog with remediation. It also explains the outline shape and the leading_callout/leading_content deduplication behavior. No contradiction with annotations.

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

Conciseness4/5

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

The description is long but every section earns its place: examples, when-to-use, section boundaries, errors, return format, outline shape. It is front-loaded with the core purpose and examples before the detailed error catalog. The only minor deduction is that the error list and outline shape sections are dense and could arguably be trimmed, but for a tool with 7 parameters and multiple modes, this level of detail is justified.

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

Completeness5/5

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

For a 7-parameter tool with no output schema, the description is remarkably complete. It covers return formats for every mode, the outline JSON shape, error conditions with remediation, paging behavior, and the relationship to sibling tools. An agent has everything needed to invoke this tool correctly and interpret its results. The absence of an output schema is fully compensated by the detailed 'Returns' and 'Outline shape' sections.

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

Parameters4/5

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

Schema description coverage is 100%, so the baseline is 3. The description adds value beyond the schema by explaining how parameters interact (mutual exclusivity, heading_level only applies with heading, start_line/limit not valid in JSON modes), and by giving concrete usage examples that show parameter combinations. It doesn't add much per-parameter syntax detail because the schema already covers that, but the interaction semantics are genuinely useful.

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

Purpose5/5

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

The description opens with a specific verb and resource: 'Read a markdown note by its vault-relative path.' It immediately distinguishes itself from siblings by naming vault_read_file for non-markdown files, vault_search for path-unknown cases, and vault_get_memory for About Me files. The multiple mode examples (properties_only, outline, heading) make the tool's scope unmistakable.

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

Usage Guidelines5/5

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

The 'When to use' paragraph is exemplary: it states the exact condition ('You know the exact path and need a specific note's content'), gives a decision procedure for large notes (outline first, then heading), and explicitly names alternatives with their selection criteria ('Prefer vault_search when you don't know the path. Prefer vault_get_memory for About Me/ files...'). It also tells when to use paging and how to check line counts cheaply.

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

vault_recent_notesRecent NotesA
Read-onlyIdempotent

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

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

When to use: Catching up on vault changes, finding recent work, or orienting after a break. Prefer vault_search for content-based discovery. Prefer vault_search_by_folder for browsing a specific folder.

Parameters:

  • sort_by + limit interact: "modified" (default) uses filesystem mtime, so every note has a value and limit works predictably. "created" uses the frontmatter created property — notes without it sort last (not excluded), so a small limit may return only notes that have the property; increase limit or use "modified" for broader coverage.

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

Errors:

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

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

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax results (default 20, no upper cap)
sort_byNoSort order (default "modified")modified

TDQS

A5/5.0
Behavior5/5

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

Annotations already mark this read-only and idempotent, and the description adds valuable behavior beyond that: 'created' sorts notes without the property last rather than excluding them, 'modified' catches sync touches and property changes, and an empty vault returns an empty array. These behavioral details will help an agent predict results accurately.

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

Conciseness5/5

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

The description is organized into clear sections: overview, examples, when to use, parameter interactions, errors, and return format. It is longer than a one-liner, but every sentence adds needed operational context, and the core purpose is front-loaded.

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

Completeness5/5

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

Despite having no output schema, the description fully documents the return shape: JSON array of note metadata with field names and nullability. It also covers empty-vault behavior and parameter edge cases, so an agent has enough information to call and interpret the tool correctly.

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

Parameters5/5

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

Although the input schema already covers parameter names and defaults, the description explains how sort_by and limit interact, including the pitfall that a small limit with sort_by 'created' may return only notes that have the property. This adds important meaning beyond the schema's minimal descriptions.

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

Purpose5/5

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

The description states a specific verb and resource: 'List recently modified or created notes, sorted by timestamp.' It also distinguishes itself from a date-range filter and shows examples, making the tool's purpose unambiguous and distinct from sibling search tools.

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

Usage Guidelines5/5

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

The description includes a dedicated 'When to use' section and explicitly names alternatives: 'Prefer vault_search for content-based discovery. Prefer vault_search_by_folder for browsing a specific folder.' This gives clear routing guidance and conditions for choosing this tool versus siblings.

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

vault_replace_in_noteReplace in NoteA
Destructive

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

Example: vault_replace_in_note({ path: "Projects/plan.md", old_text: "TODO: write summary", new_text: "Summary complete." })

When to use: Targeted text changes within a single location — fixing typos, updating values, renaming terms, or removing a short line (new_text=""). Replaces text in place; does not move content across sections. To delete a large multi-line block, prefer vault_delete_span (short anchors instead of full old_text). To replace a large block by anchors instead of reproducing the full old_text, use vault_replace_span. To relocate content between headings, 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 is matched in the body only — frontmatter properties are never searched. Include enough surrounding context to ensure uniqueness when the target text appears in multiple places.

  • old_text + new_text together determine the operation: a non-empty new_text is an edit; an empty new_text ("") is a deletion. No regex — exact text only.

  • replace_all_occurrences (default false) replaces only the first match — a safety default when old_text appears in multiple places. Set true for deliberate bulk renames or term replacements.

Errors:

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

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

  • "oldText cannot be empty" — old_text must be at least one character

  • "absolute path blocked" — the path starts at the filesystem root; use a vault-relative path

  • "hidden path blocked" — the path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; hidden paths are not editable, matching Obsidian

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

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

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

Returns: Confirmation message with replacement count (number of occurrences replaced).

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "Projects/plan.md")
new_textYesReplacement text. Empty string ("") deletes the matched text.
old_textYesExact text to find (case-sensitive). Matches in the body only — text inside frontmatter properties is not searched.
replace_all_occurrencesNoReplace all occurrences (default: false — replaces first occurrence only)

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already declare destructive=true and idempotent=false, but the description adds substantial context beyond them: YAML normalization to block style on first edit, properties are preserved, exact/case-sensitive matching with no regex, and a full error catalogue including concurrency ('concurrent write in progress'), path blocking, and control-character rejection. It also warns about Obsidian syntax pitfalls (#, [[, %%) in replacement text.

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

Conciseness4/5

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

It is long but well-organized and front-loaded: purpose, example, when-to-use, parameters, errors, returns. Every section earns its place, though the error enumeration and Obsidian-syntax note make it heavier than strictly necessary for a single-purpose replace tool.

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

Completeness5/5

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

For a destructive find-and-replace tool with no output schema, the description covers everything an agent needs: scope (body vs frontmatter), side effects (YAML normalization), operation semantics, failure modes, and return value (confirmation with replacement count). Nothing material is missing.

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

Parameters4/5

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

Schema description coverage is 100%, so the baseline is 3, but the description adds real meaning on top: it explains the old_text+new_text pairing semantics (non-empty = edit, empty = deletion), the rationale for the replace_all_occurrences safety default, and guidance to include surrounding context for uniqueness when text repeats. The uniqueness guidance is not present in the schema.

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

Purpose5/5

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

The description states a specific verb and resource (find and replace text in a markdown note's body) and immediately scopes it against siblings (body-only, properties must go through vault_update_properties or vault_write_note). 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.

Usage Guidelines5/5

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

It gives an explicit 'When to use' clause with concrete examples (typos, value updates, term renames, short-line deletion via new_text="") and names three alternatives with the conditions that select them (vault_delete_span for large blocks, vault_replace_span for anchor-based block replacement, vault_patch_note add-before-delete for relocation). This is exactly the when/when-not/alternatives guidance the dimension asks for.

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

vault_replace_spanReplace SpanA
Destructive

Replace a contiguous block of whole lines in a note's body with new content, identified by short anchor substrings instead of the block's full text. Each anchor locates a full line — the entire line is selected, not just the matching substring. Same anchor semantics as vault_delete_span. Case-sensitive matching. Properties are preserved; YAML formatting may be normalized to block style on first edit. Operates on the body only.

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

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

Parameters:

  • start_anchor + end_anchor define a line range, not a text range — each anchor locates a full line, and the entire line from start to end is replaced (never cuts mid-line). Omit end_anchor for a single-line replace.

  • end_anchor is searched at or after the start line, so the span can never run backward. If both match the same line, only that one line is replaced.

  • content replaces the entire matched span and must be non-empty. A trailing newline adds a blank line after the new block.

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

  • Blank-line runs left by the replacement are collapsed to a single blank line.

Errors:

  • "note not found" — verify path with vault_list_notes

  • "anchor not found" — fragment not on any line; verify with vault_read_note

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

  • "absolute path blocked" — the path starts at the filesystem root; use a vault-relative path

  • "hidden path blocked" — the path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; hidden paths are not editable, matching Obsidian

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

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

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

Returns: Confirmation message "Replaced lines with lines in " — N counts the lines the span covered, M the lines content supplied.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension (e.g. "Tracker.md", "Notes/Plan.md")
contentYesReplacement content (one or more lines) — replaces every line of the matched span. Must be non-empty; use vault_delete_span to delete without replacement.
end_anchorNoShort, 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.
first_matchNoIf an anchor matches more than one line, use the first match instead of erroring (default: false — ambiguity is an error).
start_anchorYesShort, unique substring that identifies the first line of the block (case-sensitive). The entire line is selected, not just the substring. Pick a brief fragment — do not paste the whole block.

TDQS

A4.9/5.0
Behavior5/5

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

Annotations cover the safety profile (destructiveHint=true, readOnlyHint=false), and the description adds substantial context beyond them: line-granular (never mid-line) replacement, property preservation with YAML normalization to block style, blank-line collapse, concurrency retry behavior, hidden/absolute path blocking, and control-character rejection. It also documents the return confirmation string.

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

Conciseness4/5

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

Front-loaded with purpose, then worked examples, usage routing, parameter behavior, errors, and syntax warnings in a scannable order. It is on the long side — the error catalogue and Obsidian syntax note could be trimmed — but nearly every line carries actionable information for a destructive, anchor-based edit.

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

Completeness5/5

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

For a 5-parameter destructive mutation with no output schema, the description covers trigger conditions, anchor semantics, edge cases, failure modes and their remedies, escaping hazards, and the exact return string. An agent has everything needed to call it correctly and recover from errors.

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

Parameters5/5

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

Schema coverage is 100%, so baseline is 3, but the description adds cross-parameter semantics the schema cannot express: end_anchor is searched at or after the start line (span cannot run backward), same-line matches collapse to one line, first_match applies independently to each anchor, and a trailing newline in content adds a blank line. This is genuine added meaning, not restatement.

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

Purpose5/5

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

Names a specific verb (replace) and resource (a contiguous span of whole lines in a note's body) and immediately differentiates its anchor-based identification scheme from full-text matching. It also names sibling alternatives (vault_replace_in_note, vault_delete_span), so an agent can place it precisely in the tool family.

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

Usage Guidelines5/5

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

A dedicated 'When to use' section states the triggering condition (replacing a block you already read where reproducing old_text exactly would be error-prone) and explicitly routes to vault_replace_in_note for typo/rename edits and vault_delete_span for removal-without-replacement. Error entries add recovery guidance (verify with vault_read_note / vault_list_notes, retry on concurrent write).

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

vault_search_by_folderSearch by FolderA
Read-onlyIdempotent

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

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

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

Parameters:

  • folder is matched as a path prefix; pass it without a trailing slash ("Projects").

  • recursive (default true) includes all nested subfolders; set false to list only the folder's top level.

  • limit (default 20) caps results.

Errors:

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

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

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax results (default 20)
folderYesFolder path (e.g. "Projects", "About Me")
recursiveNoInclude subfolders (default: true)

TDQS

A4.6/5.0
Behavior4/5

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

Annotations already declare readOnly=true, destructive=false, idempotent=true. Description adds error behavior ('empty or nonexistent folder returns empty array'), sorting ('sorted by most recently modified'), and defaults for recursive/limit, and parameter matching detail – goes beyond annotations without contradiction.

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

Conciseness4/5

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

Description is well-structured with sections (example, when to use, parameters, errors, returns). It's longer than minimal but each section adds value; front-loaded with purpose and differentiation. No fluff.

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

Completeness5/5

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

Comprehensive: covers return format (JSON array with fields), sorting, error handling, parameter behavior, and examples. No output schema exists, so description fully carries return type explanation. Enough for an agent to call correctly.

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

Parameters4/5

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

Schema covers 100% of parameters with descriptions, but description adds crucial nuance: folder is matched as a path prefix and should be passed without trailing slash, and explains limit caps results. This is beyond schema's generic 'Folder path' description.

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

Purpose5/5

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

States 'Browse notes in a folder with full metadata' and explicitly contrasts with vault_list_notes which 'returns paths only' – clear verb, resource, and differentiation from sibling.

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

Usage Guidelines5/5

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

Explicitly states 'When to use: Exploring a folder's contents with full context' and gives 'Prefer vault_list_notes when you only need paths' and 'Prefer vault_search when you have a text query' plus backlinks/outgoing links – clear guidance and alternatives.

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

vault_search_by_propertySearch by PropertyA
Read-onlyIdempotent

Find notes where a frontmatter property matches a value — metadata-only search, no text query needed. Handles both scalar properties (status: "active") and array properties (tags, related): for arrays, matches if any element equals the value (contains check, not exact array match). Matching is exact and case-sensitive; an unknown key or unmatched value returns an empty array, not an error.

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 + value are both exact and case-sensitive — no partial matching or globbing. All property values are compared as strings, so numeric or boolean properties must be passed as their string representation.

  • For array properties (tags, related), value is tested against each element individually (contains check) — "blog" matches a note with tags: ["blog", "draft"] but not tags: ["my-blog"].

  • folder narrows results to a subtree; omit for vault-wide search. Combined with key+value, this lets you check how a property is used within a specific area.

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

ParametersJSON Schema
NameRequiredDescriptionDefault
keyYesProperty key name (e.g. "status", "type", "tags"). Use vault_list_property_keys to discover valid keys.
limitNoMax results (default 20). Increase for broad metadata queries.
valueYesValue to match (exact, case-sensitive, e.g. "active", "session-log"). Use vault_list_property_values to discover valid values for a key.
folderNoRestrict to a folder prefix (e.g. "Projects")

TDQS

A4.9/5.0
Behavior5/5

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

Discloses important behavior beyond annotations: exact and case-sensitive matching, contains semantics for arrays, string coercion for numeric/boolean values, empty-array return instead of error, and mtime-descending sort order. With no behavioral annotations provided, this fully compensates.

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

Conciseness4/5

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

The description is long but almost every sentence adds value. The array-contains behavior is explained twice (once up front and once under parameters), which is a minor redundancy, and the structure could be tightened slightly. Overall it is dense and well-organized.

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

Completeness5/5

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

Covers purpose, exact matching semantics, array behavior, error behavior, sorting, parameter guidance, and sibling-tool routing. Combined with the schema, an agent has everything needed to call this correctly.

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

Parameters5/5

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

The schema only defines types and required/optional; the description adds critical semantics: key/value are exact and case-sensitive, array values are matched per element, folder narrows to a subtree, and numeric/boolean values must be passed as strings. This is far beyond the schema.

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

Purpose5/5

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

The first sentence states exactly what the tool does: find notes by frontmatter property value, with no text query needed. It distinguishes this from vault_search and vault_search_by_tag, and the examples make the purpose concrete.

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

Usage Guidelines5/5

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

Explicitly says when to use it (metadata-only search without a text query) and when not to: prefer vault_search when a text query exists, prefer vault_search_by_tag for tag-specific searching, and use vault_list_property_keys/values for discovery. This is model-level guidance an agent can act on directly.

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

vault_search_by_tagSearch by TagA
Read-onlyIdempotent

Find notes with a specific tag. By default uses hierarchical prefix matching — a parent tag matches all children (e.g. "project" matches "project/vault-cortex", "project/blog"). Set exact=true for exact match only.

Example: vault_search_by_tag({ tag: "project" }) returns all notes tagged project or project/*.

When to use: Exploring tag hierarchies or finding all notes with a specific tag, without needing a text query. Prefer vault_search when you also need text-based relevance ranking. Use vault_list_tags first to discover available tags.

Parameters:

  • tag is the bare tag name without a leading "#" ("project", not "#project"). Hierarchical tags use "/" separators ("project/vault-cortex").

  • tag + exact interact: with exact=false (default), "project" matches "project", "project/vault-cortex", "project/blog" — the match is prefix-based on the "/" separator, so "project" does NOT match "my-project" or "projects". Set exact=true to match only the literal tag, excluding children.

Errors:

  • An unknown tag or no matches returns an empty array, not an error — don't use as an existence check.

Returns: JSON array of up to 20 notes' metadata (path, title, tags, related, folder, type, created, modified, bytes, leading_callout?, additional_properties), sorted by most recently modified. bytes is the on-disk file size. Promoted keys are in top-level fields; additional_properties contains only unpromoted keys.

ParametersJSON Schema
NameRequiredDescriptionDefault
tagYesTag name without "#" prefix (e.g. "project", "session-log"). Hierarchical tags use "/" separators (e.g. "project/vault-cortex").
exactNoExact match only (default: false, prefix match)

TDQS

A5/5.0
Behavior5/5

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

Annotations already declare readOnlyHint, openWorldHint=false, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description adds significant behavioral detail beyond annotations: hierarchical prefix matching by default, exact=true behavior, prefix matching is separator-aware so 'project' does not match 'my-project', unknown tags return an empty array rather than an error, results are capped at 20, and sorting is by most recently modified. No contradictions with annotations.

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

Conciseness5/5

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

The description is lengthy but every section earns its place: core behavior, a concrete example, when-to-use guidance, parameter semantics, error behavior, and return format. It is logically organized with clear headings and the most important behavioral details are front-loaded. The repetition of parameter details already in the schema is minimal and always adds clarity through examples or edge-case explanations.

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

Completeness5/5

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

Despite lacking an output schema, the description fully covers what an agent needs to invoke the tool correctly: required naming conventions, the exact matching semantics, error behavior, result limit, sort order, and available return fields. It even explains the meaning of bytes and promoted keys. No meaningful gap remains for effective selection and invocation.

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

Parameters5/5

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

Schema coverage is 100%, but the description substantially extends parameter meaning. It explains that the tag should be bare without '#', uses '/' separators, clarifies how tag and exact interact with concrete examples, and explicitly states that prefix matching is based on the '/' separator so it does not match partial words. This goes well beyond the schema's brief descriptions.

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

Purpose5/5

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

The description opens with 'Find notes with a specific tag,' which clearly states the verb and resource. It distinguishes itself from sibling tools by mentioning vault_search (text-based relevance ranking) and vault_list_tags (discovering available tags), making its role in the tag-search niche unambiguous.

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

Usage Guidelines5/5

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

The 'When to use' section explicitly states use cases for exploring tag hierarchies or finding all notes with a tag. It also gives exclusions: prefer vault_search when text relevance ranking is needed, and use vault_list_tags first to discover tags. The error note warns against using it as an existence check, providing clear guidance on when not to rely on it.

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

vault_update_memoryUpdate MemoryA
Idempotent

Append a dated entry to a section of a About Me/ memory file. The server prefixes the date automatically ("- YYYY-MM-DD: entry text") and inserts newest-first by default. Idempotent — an exact duplicate (same date + text in the same section) is a no-op, so retrying a timed-out call is safe. Memory files are append-only by default: when a preference changes, append the new state (newest wins) rather than deleting the old one. A file may declare entry-policy: living in frontmatter (surfaced by vault_list_memory_files) — a current-state file where pruning expired entries is expected maintenance rather than a violation.

Example: vault_update_memory({ file: "Opinions", section: "Code patterns (newest first)", entry: "Prefer immutable data structures" })

When to use: Recording a new preference, principle, opinion, or fact about the user. Call vault_list_memory_files first and reuse existing file and section names so entries stay grouped. Prefer vault_write_note for creating non-memory notes. A missing file or section is created automatically (new sections get "(newest first)" appended; new files get a placeholder scope callout to fill in via vault_replace_in_note). A new section name that is nearly identical to an existing heading (an HTML-entity slip, typo, or spacing variation) is rejected instead of created, so a mistyped name cannot silently fragment the file — names differing only in digits (e.g. "2025" vs "2026") are treated as distinct.

Parameters:

  • options.date — ISO YYYY-MM-DD, defaults to today (server timezone).

  • options.position — "top" (default, newest-first) inserts above existing entries; "bottom" appends below them.

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

Errors:

  • "refusing memory write: … would shrink content" — safety guard for diverged on-disk content. Re-read with vault_get_memory before retrying.

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

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

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

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

  • "memory file must not start with a dot" — a dot-prefixed name would create a hidden file (invisible in Obsidian and to every listing); choose a visible name.

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

Returns: Confirmation message (notes when an identical entry already existed and nothing was written).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesMemory file name without .md (e.g. "Principles")
entryYesRaw entry text — a single line (newlines are rejected); the server prepends "- **YYYY-MM-DD**: " automatically. Do not include the date or bullet prefix.
optionsNoOptional date and position overrides
sectionYesH2 section heading (e.g. "Decision heuristics (newest first)"). Matched case-insensitively, with or without the "(newest first)" suffix.

TDQS

A5/5.0
Behavior5/5

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

Even though annotations already set idempotentHint=true, the description adds critical context: the append-only model, the idempotent no-op on exact duplicates, the server-side date prefixing, and the near-duplicate rejection mechanism. It also explains error scenarios and the meaning of the confirmation message, going well beyond the annotations.

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

Conciseness5/5

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

The description is long but every section earns its place: core usage, example, when-to-use, parameter clarifications, error catalogue, and return behavior. Information is front-loaded with the action and scope, and organized with clear headings, making it easy to scan. No redundancy or filler.

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

Completeness5/5

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

With four parameters, nested options, idempotency, append-only semantics, and multiple error modes, the description covers all necessary information: parameter semantics, usage rules, error handling, and return value. There is no output schema, so the description fully compensates by explaining what the confirmation message notes. An agent can use this tool correctly with zero guesswork.

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

Parameters5/5

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

Schema coverage is 100%, but the description enriches every parameter: it clarifies that the server prepends the date and bullet, instructs not to include them in 'entry', details the date format and default, explains position meaning, and describes the case-insensitive section matching with suffix handling. This is far more than the schema alone provides.

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

Purpose5/5

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

The description begins with a specific verb ('Append a dated entry') and resource ('a section of a About Me/memory file'), clearly distinguishing it from sibling write tools like vault_write_note, which it explicitly names as the alternative for non-memory notes. It also provides an example call, leaving no ambiguity about what the tool does.

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

Usage Guidelines5/5

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

The description has a dedicated 'When to use' section that instructs the agent to call vault_list_memory_files first and reuse existing file/section names, and explicitly contrasts with vault_write_note. It also covers edge cases like missing files/sections and the near-duplicate guard, giving comprehensive guidance on when and how to invoke the tool.

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

vault_update_propertiesUpdate PropertiesA
DestructiveIdempotent

Update a note's frontmatter properties via shallow merge — new keys added, matching keys overwritten, null deletes a key, unmentioned keys preserved. Body is never modified.

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

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 }) — arrays are replaced entirely, not appended to.

Errors:

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

  • "absolute path blocked" — the path starts at the filesystem root; use a vault-relative path

  • "path traversal blocked" — path escapes vault root

  • "hidden path blocked" — the path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; hidden paths are not editable, matching Obsidian

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

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

Returns: Confirmation message.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesVault-relative path to the note, including the ".md" extension
propertiesYesProperties to merge. New keys are added; existing keys are overwritten; a null value deletes that key; unmentioned keys are preserved.

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already declare destructiveHint=true and idempotentHint=true, but the description goes well beyond them: it specifies shallow-merge semantics, that null deletes a key while unmentioned keys survive, that the body is never modified, a concurrent-write conflict condition, hidden-path blocking parity with Obsidian, and that arrays are replaced rather than appended. It also discloses the return value in the absence of an 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.

Conciseness4/5

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

The core semantic contract and an example are front-loaded, then usage, errors, syntax, and return value, so it is easy to scan. The error enumeration is fairly long but each entry is actionable rather than filler; a slightly tighter grouping would make it ideal.

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

Completeness5/5

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

For a two-parameter mutation tool with a nested free-form object and no output schema, this covers the semantics, an example, prerequisites, error taxonomy, and syntax pitfalls. Nothing an agent needs to invoke it correctly is missing, including the fact that the response is just a confirmation message.

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

Parameters4/5

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

Schema description coverage is 100%, so the baseline is 3, but the description adds genuinely new semantics not present in the schema: a concrete call example, the warning that arrays are replaced entirely rather than appended, wikilink quoting requirements, and the risk of silent query failures from type mismatches. Only the top-level merge behavior is repeated from the schema.

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

Purpose5/5

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

The opening sentence gives a precise verb+resource ('Update a note's frontmatter properties') and immediately defines the merge semantics, including that the body is never touched. It is clearly distinguishable from body-writing siblings like vault_write_note and vault_replace_in_note. An agent knows exactly what class of operation this is before opening the schema.

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

Usage Guidelines5/5

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

It has an explicit 'When to use' block (tags, status, type, any property without rewriting the body), names a specific alternative and its trigger condition ('Prefer vault_write_note when creating a new note, or replacing the body with overwrite: true'), and prescribes a prerequisite read via vault_read_note({ properties_only: true }). The only minor gap is that the closely-named sibling vault_patch_note is not contrasted.

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

vault_update_taskUpdate TaskA
Destructive

Update a task's status, priority, description, dates, dependencies, block_id, checklist items, or heading placement in one call. Any combination of these can change together — every field passed is written in a single edit.

Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", status: "done" }) — complete a task; on a Kanban board, auto-moves to the done lane; a recurring task (🔁) spawns its next occurrence Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", recurrence: "every week" }) — make a task recurring (null removes the rule) Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", heading: "Done" }) — move a task to a different heading (lands at the top of the lane by default) Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", heading: "Done", position: "bottom" }) — move to the bottom of the lane Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", description: "Updated task name", due: "2026-10-01" }) — change description and set due date Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", due: null }) — clear a date field Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", status: "in_progress", add_subtasks: ["Design", "Implement", "Test"] }) — start working and add checklist stages Example: vault_update_task({ path: "TASKS.md", line: 42, assign_block_id: "my-task" }) — add a block_id to a task that lacks one Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", task_id: "abc123" }) — set a Tasks plugin 🆔 identifier Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", on_completion: "delete" }) — set the task to be removed on completion (null clears the field)

When to use: Any change to an existing task — completing, starting, re-prioritizing, editing text, setting or clearing dates, adding checklist items, assigning block_ids, or moving between headings. 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:

  • path (required): vault-relative path to the note (must end in ".md").

  • Exactly one of block_id or line is required to identify the task.

  • At least one change is required. Every field passed is applied in the same single write:

    • status: "todo" | "in_progress" | "done" | "cancelled". Manages checkbox and done/cancelled dates. Kanban: "done" moves the card and its checklist sub-items to the done lane (sub-item checkboxes left as they are); a sub-task stays under its parent. Recurring (🔁): spawns the next occurrence above the completed one (below with the plugin's "next line" setting), dates advanced per the rule. The spawn stays in the source lane with no block_id, 🆔, or ⛔ — follow up with assign_block_id on next_occurrence.line. Completing by line is NOT idempotent for recurring tasks (the spawn occupies the old line); prefer block_id. Delete (🏁): removes the task line and children instead of moving to done. With 🔁 + 🏁, the spawn is created first, then the completed line is removed; with "next line", children transfer to the spawn. Result carries on_completion_applied: "delete".

    • priority: "highest" | "high" | "medium" | "low" | "lowest" sets the signifier; null removes it.

    • recurrence: sets the Tasks plugin 🔁 rule, in natural language ("every week", "every month on the 15th", "every 3 days when done" — "when done" bases the next occurrence on the completion day); null removes it. Passed together with status "done", the new rule governs the spawn (recurrence: null completes without spawning).

    • on_completion: "delete" or "keep" — sets the Tasks plugin 🏁 action applied when the task is completed; null removes it. When on_completion and status are passed in the same call, the submitted on_completion value governs the delete decision — setting "keep" while completing a "delete" task prevents the deletion. See the status bullet for delete behavior details.

    • description: replaces the task text. Metadata fields and block_id are preserved.

    • due / scheduled / start / created: YYYY-MM-DD sets the date; null clears it.

    • task_id: string sets the Tasks plugin 🆔; null clears it.

    • depends_on: non-empty string array sets the Tasks plugin ⛔; null clears it.

    • add_subtasks: non-empty string array — appends one indented [ ] checklist item per entry under the task; existing checklist items are kept. For full sub-tasks with their own metadata, use vault_create_task with parent_block_id.

    • assign_block_id: adds or replaces the ^block-id on the task line. Letters, digits, and hyphens only; must be unique within the note.

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

    • position: "top" or "bottom" — where within the target heading the task lands after a heading move or auto-done-lane move. Defaults to "top" (first position in the lane). Ignored when no heading move occurs.

    • Clearing is always explicit null — omitting a field leaves it untouched.

  • format: "emoji" or "dataview" — overrides the auto-detected Tasks plugin format.

Errors:

  • "note not found" — path does not exist

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

  • "blockId ... not found" — no task line in the note ends with ^block_id

  • "no task at line N" — line doesn't contain a task checkbox

  • "at least one mutation" — no change params provided

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

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

  • "multiple done lanes detected" — status "done" on a Kanban board with more than one Complete-marked lane; pass heading to pick the lane

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

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

  • "invalid date" — a date param fails calendar validation

  • "description cannot be empty" / "dependsOn cannot be empty" / "addSubtasks cannot be empty" / "addSubtasks cannot contain an empty item" — whitespace-only text or an empty array (use null to clear depends_on)

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

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

  • "unrecognized recurrence rule ..." — the rule text is not Tasks-plugin natural language; written as-is it would silently never recur

  • A recurring task completed with a rule that yields no next occurrence (unreadable rule text already on the line, or a finite rule with no dates left) still completes — the result carries an advisory instead of an error

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

Obsidian syntax: The Tasks plugin reads metadata off the END of a task line, so a trailing run of signifier syntax inside description or add_subtasks text — an emoji field like "🔁 every week", or a Dataview [key:: value] field, followed only by other recognized task fields — is read back as task metadata rather than text. A signifier followed by ordinary prose stays description text unless the prose matches that field's value grammar — a 🔁 recurrence reads any trailing words as its rule, while a 📅 followed by ordinary words stays description text because the words are not a date. The same interference can change the value an adjacent field reads back with, or make a field appear that was never set, as the 🔁 example shows. The write still succeeds either way; when the stored line would read back differently than this call set, the result carries an advisories array naming each divergence. The dates a status change stamps or clears (the ✅/❌ dates) are expected and produce no advisories on their own — but a description signifier that changes what the stamped date parses back as is still reported.

Returns: JSON { path, line, description, block_id, heading, subtasks, next_occurrence, changes, advisories, on_completion_applied } — line is the final 1-based position (when on_completion_applied is "delete", it is the position the task occupied before removal); description is the current text; block_id and heading reflect the task after the update (block_id is omitted when the task has none, heading when the task sits above the first heading); subtasks lists each checklist item added by add_subtasks as { line, description } (omitted when none were added) — checklist items carry no block_id, so line is the handle for a follow-up update; next_occurrence is present only when a completion spawned a recurring task's next occurrence: { line, description, due?, scheduled?, start? } with only the dates the occurrence has — it carries no block_id, so line is its handle; changes lists every field applied as "field: before → after", with "(none)" for an absent value (for subtasks the two sides are checklist-item counts, and a spawn adds "next_occurrence: (none) → line N"); advisories (omitted when the line round-trips clean and no recurrence notice applies) lists one sentence per place the stored line parses back differently than this call set (see Obsidian syntax above) or per recurrence event that did not produce a next occurrence; on_completion_applied (present only when the effective on_completion was delete — pre-existing on the task or set in the same call — and it was transitioned to done) is always "delete".

ParametersJSON Schema
NameRequiredDescriptionDefault
dueNoDue date (YYYY-MM-DD) to set, or null to clear.
lineNo1-based line number from vault_list_tasks. Fragile if the file changed since the query.
pathYesVault-relative path to the note containing the task (must end in ".md")
startNoStart date (YYYY-MM-DD) to set, or null to clear.
formatNoField format for new metadata. Default: auto-detected from .obsidian/ config, falling back to emoji.
statusNoTarget 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.
createdNoCreated date (YYYY-MM-DD) to set or clear. Typically auto-stamped; use for corrections.
headingNoTarget heading to move the task to. On Kanban boards this is a lane move; works on any note with headings. Not valid on sub-tasks.
task_idNoTasks plugin 🆔 identifier to set, or null to clear.
block_idNoStable task identifier — the ^block-id at the end of the task line, without the ^. Preferred over line.
positionNoWhere within the target heading the task lands after a heading move or auto-done-lane move. Defaults to "top". Ignored when no heading move occurs.top
priorityNoPriority signifier to set, or null to remove it.
scheduledNoScheduled date (YYYY-MM-DD) to set, or null to clear.
depends_onNoTasks plugin ⛔ dependency IDs to set (non-empty), or null to clear.
recurrenceNoTasks plugin 🔁 rule in natural language (e.g. "every week", "every 2 weeks when done") to set, or null to remove it. Completing the task spawns its next occurrence.
descriptionNoNew task description text. Replaces the existing description; metadata fields and block_id are preserved.
add_subtasksNoChecklist 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.
on_completionNoTasks 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.
assign_block_idNoAdd or replace the ^block-id on the task line. Letters, digits, and hyphens only; must be unique within the note.

TDQS

A4.9/5.0
Behavior5/5

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

The description goes far beyond the annotations: it explains Kanban lane movement, recurrence spawning, delete-on-completion behavior, non-idempotency when completing recurring tasks by line, explicit-null clearing semantics, concurrent-write errors, and even the Obsidian parser interference that can change how stored text reads back. This is rich, honest behavioral disclosure consistent with destructiveHint=true and 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.

Conciseness4/5

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

The description is very long, but the tool has 19 parameters, complex side effects, and a large error surface, so most of the length is justified. It is front-loaded with a summary and concrete examples, then organized into clear sections. A small amount of redundancy with the schema and among examples keeps it from a perfect score.

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

Completeness5/5

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

With no output schema, the description fully specifies the return JSON shape, including optional fields like next_occurrence, advisories, and on_completion_applied. It also enumerates all error conditions and explains the Obsidian syntax edge case. For a tool this complex, nothing essential for correct invocation appears to be missing.

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

Parameters5/5

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

Although the input schema already documents all parameters (100% coverage), the description adds substantial meaning: exactly one of block_id or line is required, at least one mutation is required, clearing is always explicit null, and the examples demonstrate valid combinations. It also adds cross-parameter behavior such as recurrence interacting with status 'done', add_subtasks appending without replacing, and position being ignored without a heading move.

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

Purpose5/5

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

The description opens with a precise verb and resource: 'Update a task's status, priority, description, dates, dependencies, block_id, checklist items, or heading placement in one call.' It names the exact object (a task) and the exact scope of what can change, and the examples reinforce this. It also differentiates from the nearest sibling by explicitly directing creation to vault_create_task.

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

Usage Guidelines5/5

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

The 'When to use' section is explicit: any change to an existing task, with a concrete list of cases. It also tells the agent to call vault_list_tasks first to obtain identification fields, and to use vault_create_task instead for new tasks. This leaves no ambiguity about when this tool should be selected over its siblings.

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

vault_write_noteWrite NoteA
Destructive

Create a markdown note. Errors if a note already exists at the path unless overwrite is set. Body replaces the entire note content — this is a full write, not a partial edit. Properties are passed separately and merged with any existing properties when overwriting (new keys added, matching keys overwritten, keys set to null removed, unmentioned keys preserved).

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

When to use: Creating a new note. Set overwrite: true only when you intend to replace an existing note's body. Prefer vault_update_properties for property-only edits (no body round-trip). Prefer vault_update_memory for appending dated entries to About Me/ memory files.

Limitation: Writes the entire body. Do not use for surgical edits to large files — existing content will be lost unless you include it in the body parameter.

Errors:

  • "note already exists" — a note already lives at this path; set overwrite: true to replace it, or use vault_patch_note / vault_replace_in_note for partial edits

  • "absolute path blocked" — the path starts at the filesystem root; use a vault-relative path

  • "hidden path blocked" — the path targets a hidden (dot-prefixed) file or folder like ".obsidian/"; hidden paths are not writable, matching Obsidian

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

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

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: Confirmation message.

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyYesMarkdown body content — do not include frontmatter fences (---); use the properties parameter instead.
pathYesVault-relative path including the ".md" extension (e.g. "Projects/notes.md"). Parent folders are created as needed.
overwriteNoAllow overwriting an existing note (default: false — errors if file exists).
propertiesNoOptional 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.

TDQS

A4.8/5.0
Behavior5/5

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

Annotations only cover the safety profile (destructive, non-idempotent, closed-world). The description goes well beyond: it discloses overwrite-gated error behavior, full-body replacement semantics, property merge rules (add/overwrite/null-delete/preserve), five named error strings with recovery guidance, and hidden-path restrictions. This is rich behavioral context the 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.

Conciseness4/5

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

Front-loaded with the core semantic (full write vs. partial edit) and organized into When-to-use, Limitation, Errors, and syntax sections. The two examples and the long error enumeration are useful but make it heavier than strictly necessary; still, every block earns its place for a destructive tool.

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

Completeness5/5

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

For a 4-parameter destructive write with no output schema, the description covers everything an agent needs: overwrite semantics, property merge behavior, failure modes with recovery, path constraints, and the return value ('Confirmation message'). No output schema exists, so return-format detail is not owed.

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

Parameters4/5

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

Schema coverage is 100%, so baseline is 3, but the description adds meaning beyond the schema: body is a complete replacement rather than a merge, properties are merged rather than replaced, and the error text for overwrite=false is spelled out. It slightly reinforces rather than extends some schema text, keeping it out of 5 territory.

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

Purpose5/5

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

States a specific verb and resource ('Create a markdown note') and immediately distinguishes itself from partial-edit siblings by declaring 'this is a full write, not a partial edit.' An agent can tell it apart from vault_patch_note, vault_replace_in_note, and vault_update_properties without opening any schema.

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

Usage Guidelines5/5

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

Explicit 'When to use' section names the condition for overwrite:true and routes two sibling cases away (vault_update_properties for property-only edits, vault_update_memory for appending dated entries). Also names a 'Limitation' telling the agent when NOT to use it for surgical edits.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.51.1
    • Changedvault_create_task1 field changed
      • addedInput schema / properties / on_completion
        Added value: +{
        +  "description": "Tasks plugin 🏁 onCompletion action. \"delete\" removes the task line on completion; \"keep\" leaves it in place.",
        +  "enum": [
        +    "delete",
        +    "keep"
        +  ],
        +  "type": "string"
        +}
    • Changedvault_update_task3 fields changed
      • changedInput schema / properties / add_subtasks / description
        Previous value: -"Checklist items to append, one indented [ ] line each, under the task's existing items — never replaces them. Can be combined with any other change. For full sub-tasks with metadata, use vault_create_task with parent_block_id."New value: +"Checklist items to append, one indented [ ] line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id."
      • addedInput schema / properties / on_completion
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "delete",
        +        "keep"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Tasks plugin 🏁 onCompletion action to set, or null to remove it. \"delete\" removes the task line on completion; \"keep\" leaves it in place (which is also the behavior when no 🏁 field exists on the task). Omitting this parameter leaves the field unchanged."
        +}
      • changedInput schema / properties / status / description
        Previous value: -"Target status. \"done\" appends the ✅ date and, on a Kanban board, moves the card and its checklist sub-items to the done lane (sub-item checkboxes are left as they are). \"cancelled\" appends the ❌ date."New value: +"Target status. \"done\" appends the ✅ date and, on a Kanban board, moves the card and its checklist sub-items to the done lane (sub-item checkboxes are left as they are); a task with 🏁 delete / [onCompletion:: delete] is removed from the file instead. \"cancelled\" appends the ❌ date."
  2. 2 tool updatesv0.51.0
    • Changedvault_create_task1 field changed
      • addedInput schema / properties / recurrence
        Added value: +{
        +  "description": "Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every 2 weeks when done\"). Completing the task spawns its next occurrence.",
        +  "minLength": 1,
        +  "type": "string"
        +}
    • Changedvault_update_task1 field changed
      • addedInput schema / properties / recurrence
        Added value: +{
        +  "anyOf": [
        +    {
        +      "minLength": 1,
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every 2 weeks when done\") to set, or null to remove it. Completing the task spawns its next occurrence."
        +}
  3. 17 tool updatesv0.50.0
    • Changedvault_delete_span1 field changed
      • addedInput schema / properties / first_match / default
        Added value: +false
    • Changedvault_find_orphans4 fields changed
      • addedInput schema / properties / limit / default
        Added value: +50
      • addedInput schema / properties / limit / maximum
        Added value: +9007199254740991
      • addedInput schema / properties / limit / minimum
        Added value: +1
      • changedInput schema / properties / limit / type
        Previous value: -"number"New value: +"integer"
    • Changedvault_insert_at_anchor1 field changed
      • addedInput schema / properties / first_match / default
        Added value: +false
    • Changedvault_list_files1 field changed
      • addedInput schema / properties / limit / default
        Added value: +50
    • Changedvault_list_property_values4 fields changed
      • addedInput schema / properties / limit / default
        Added value: +50
      • addedInput schema / properties / limit / maximum
        Added value: +9007199254740991
      • addedInput schema / properties / limit / minimum
        Added value: +1
      • changedInput schema / properties / limit / type
        Previous value: -"number"New value: +"integer"
    • Changedvault_list_tasks4 fields changed
      • addedInput schema / properties / limit / default
        Added value: +50
      • addedInput schema / properties / sort_by / default
        Added value: +"due"
      • addedInput schema / properties / status / default
        Added value: +"not_done"
      • addedInput schema / properties / top_level_only / default
        Added value: +false
    • Changedvault_memory_recall2 fields changed
      • addedInput schema / properties / limit
        Added value: +{
        +  "default": 50,
        +  "description": "Cap on returned entries (default 50). When more match, the least-relevant are dropped and truncated=true — never a date range.",
        +  "maximum": 9007199254740991,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • removedInput schema / properties / max_results
        Removed value: -{
        -  "description": "Cap on returned entries (default 50). When more match, the least-relevant are dropped and truncated=true — never a date range.",
        -  "type": "number"
        -}
    • Changedvault_recent_notes5 fields changed
      • addedInput schema / properties / limit / default
        Added value: +20
      • addedInput schema / properties / limit / maximum
        Added value: +9007199254740991
      • addedInput schema / properties / limit / minimum
        Added value: +1
      • changedInput schema / properties / limit / type
        Previous value: -"number"New value: +"integer"
      • addedInput schema / properties / sort_by / default
        Added value: +"modified"
    • Changedvault_replace_in_note1 field changed
      • addedInput schema / properties / replace_all_occurrences / default
        Added value: +false
    • Changedvault_replace_span1 field changed
      • addedInput schema / properties / first_match / default
        Added value: +false
    • Changedvault_search6 fields changed
      • removedInput schema / properties / filters / properties / include_leading_callout
        Removed value: -{
        -  "description": "If true, each result includes its leading_callout ({ type, title, body }) when present. Off by default to keep results lean.",
        -  "type": "boolean"
        -}
      • removedInput schema / properties / filters / properties / limit
        Removed value: -{
        -  "description": "Max results (default 20)",
        -  "type": "number"
        -}
      • removedInput schema / properties / filters / properties / snippet_tokens
        Removed value: -{
        -  "description": "Snippet length in tokens (default 30)",
        -  "type": "number"
        -}
      • addedInput schema / properties / include_leading_callout
        Added value: +{
        +  "default": false,
        +  "description": "If true, each result includes its leading_callout ({ type, title, body }) when present. Off by default to keep results lean.",
        +  "type": "boolean"
        +}
      • addedInput schema / properties / limit
        Added value: +{
        +  "default": 20,
        +  "description": "Max results (default 20)",
        +  "maximum": 9007199254740991,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • addedInput schema / properties / snippet_tokens
        Added value: +{
        +  "default": 30,
        +  "description": "Snippet length in tokens (default 30)",
        +  "maximum": 9007199254740991,
        +  "minimum": 1,
        +  "type": "integer"
        +}
    • Changedvault_search_by_folder5 fields changed
      • addedInput schema / properties / limit / default
        Added value: +20
      • addedInput schema / properties / limit / maximum
        Added value: +9007199254740991
      • addedInput schema / properties / limit / minimum
        Added value: +1
      • changedInput schema / properties / limit / type
        Previous value: -"number"New value: +"integer"
      • addedInput schema / properties / recursive / default
        Added value: +true
    • Changedvault_search_by_property4 fields changed
      • addedInput schema / properties / limit / default
        Added value: +20
      • addedInput schema / properties / limit / maximum
        Added value: +9007199254740991
      • addedInput schema / properties / limit / minimum
        Added value: +1
      • changedInput schema / properties / limit / type
        Previous value: -"number"New value: +"integer"
    • Changedvault_search_by_tag1 field changed
      • addedInput schema / properties / exact / default
        Added value: +false
    • Changedvault_update_memory1 field changed
      • addedInput schema / properties / options / properties / position / default
        Added value: +"top"
    • Changedvault_update_task1 field changed
      • addedInput schema / properties / position / default
        Added value: +"top"
    • Changedvault_write_note1 field changed
      • addedInput schema / properties / overwrite / default
        Added value: +false
  4. 7 tool updatesv0.41.2
    • Addedvault_create_task
    • Changedvault_delete_span2 fields changed
      • changedInput schema / properties / end_anchor / description
        Previous value: -"Short, unique substring on the LAST line of the block, searched at or after the start_anchor line. Omit to delete just the single line containing start_anchor."New 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."
      • changedInput schema / properties / start_anchor / description
        Previous value: -"Short, unique substring on the first line of the block to delete (case-sensitive). 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). The entire line is selected, not just the substring. Pick a brief fragment — do not paste the whole block."
    • Addedvault_insert_at_anchor
    • Changedvault_list_tasks5 fields changed
      • changedInput schema / properties / limit / description
        Previous value: -"Max results (default 50)"New value: +"Max results (default 50); total always reports the full match count"
      • addedInput schema / properties / limit / maximum
        Added value: +9007199254740991
      • addedInput schema / properties / limit / minimum
        Added value: +1
      • changedInput schema / properties / limit / type
        Previous value: -"number"New value: +"integer"
      • addedInput schema / properties / top_level_only
        Added value: +{
        +  "description": "When true, only top-level tasks (depth 0) are returned — excludes indented sub-tasks and checklist items. Default false.",
        +  "type": "boolean"
        +}
    • Changedvault_patch_note1 field changed
      • changedInput schema / properties / content / description
        Previous value: -"Markdown content to insert. Must not begin with the target heading text (it would duplicate the heading, which is kept automatically)."New value: +"Markdown content to insert, 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)."
    • Addedvault_replace_span
    • Changedvault_update_task18 fields changed
      • addedInput schema / properties / add_subtasks
        Added value: +{
        +  "description": "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.",
        +  "items": {
        +    "minLength": 1,
        +    "type": "string"
        +  },
        +  "minItems": 1,
        +  "type": "array"
        +}
      • addedInput schema / properties / assign_block_id
        Added value: +{
        +  "description": "Add or replace the ^block-id on the task line. Letters, digits, and hyphens only; must be unique within the note.",
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedInput schema / properties / created
        Added value: +{
        +  "anyOf": [
        +    {
        +      "minLength": 1,
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Created date (YYYY-MM-DD) to set or clear. Typically auto-stamped; use for corrections."
        +}
      • addedInput schema / properties / depends_on
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "minLength": 1,
        +        "type": "string"
        +      },
        +      "minItems": 1,
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Tasks plugin ⛔ dependency IDs to set (non-empty), or null to clear."
        +}
      • addedInput schema / properties / description
        Added value: +{
        +  "description": "New task description text. Replaces the existing description; metadata fields and block_id are preserved.",
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedInput schema / properties / due
        Added value: +{
        +  "anyOf": [
        +    {
        +      "minLength": 1,
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Due date (YYYY-MM-DD) to set, or null to clear."
        +}
      • changedInput schema / properties / format / description
        Previous value: -"Field format for new metadata (done dates, priority). Overrides the auto-detected Tasks plugin config. Default: auto-detected from .obsidian/ config, falling back to emoji."New value: +"Field format for new metadata. Default: auto-detected from .obsidian/ config, falling back to emoji."
      • addedInput schema / properties / heading
        Added value: +{
        +  "description": "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.",
        +  "minLength": 1,
        +  "type": "string"
        +}
      • removedInput schema / properties / lane
        Removed value: -{
        -  "description": "Target Kanban lane heading for a lane move. Only valid on Kanban boards.",
        -  "minLength": 1,
        -  "type": "string"
        -}
      • addedInput schema / properties / position
        Added value: +{
        +  "description": "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.",
        +  "enum": [
        +    "top",
        +    "bottom"
        +  ],
        +  "type": "string"
        +}
      • addedInput schema / properties / priority / anyOf
        Added value: +[
        +  {
        +    "enum": [
        +      "highest",
        +      "high",
        +      "medium",
        +      "low",
        +      "lowest"
        +    ],
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / priority / description
        Previous value: -"Target priority. \"none\" removes the priority emoji."New value: +"Priority signifier to set, or null to remove it."
      • removedInput schema / properties / priority / enum
        Removed value: -[
        -  "highest",
        -  "high",
        -  "medium",
        -  "low",
        -  "lowest",
        -  "none"
        -]
      • removedInput schema / properties / priority / type
        Removed value: -"string"
      • addedInput schema / properties / scheduled
        Added value: +{
        +  "anyOf": [
        +    {
        +      "minLength": 1,
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Scheduled date (YYYY-MM-DD) to set, or null to clear."
        +}
      • addedInput schema / properties / start
        Added value: +{
        +  "anyOf": [
        +    {
        +      "minLength": 1,
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Start date (YYYY-MM-DD) to set, or null to clear."
        +}
      • changedInput schema / properties / status / description
        Previous value: -"Target status. \"done\" appends ✅ date and auto-moves to done lane on Kanban boards. \"cancelled\" appends ❌ 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). \"cancelled\" appends the ❌ date."
      • addedInput schema / properties / task_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "minLength": 1,
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Tasks plugin 🆔 identifier to set, or null to clear."
        +}
  5. 1 tool updatev0.37.1
    • Changedvault_patch_note1 field changed
      • addedInput schema / properties / include_children
        Added value: +{
        +  "description": "When true, allows replace to overwrite a section that contains child headings. Without this, replace errors if children exist — preventing silent data loss.",
        +  "type": "boolean"
        +}

TDQS

A4.5/5.0

Scored across 33 tools

Disambiguation4/5

Most tools have clearly distinct purposes (read vs write vs search vs task operations), but the editing tools vault_patch_note, vault_replace_in_note, vault_delete_span, vault_replace_span, and vault_insert_at_anchor have overlapping line/text manipulation capabilities that could confuse an agent. The descriptions do a good job of differentiating them, but the boundaries between replace-by-text and replace-by-anchor are subtle.

Naming Consistency5/5

All 33 tools follow a consistent vault_verb_noun pattern (vault_list_files, vault_read_note, vault_create_task, vault_update_properties, vault_delete_memory, etc.). The naming convention is uniform and predictable, making it easy to guess tool names for new operations.

Tool Count3/5

33 tools is on the heavy side for a single server, though the vault domain is broad (notes, tasks, memory, files, search, links, properties). The count is justified by the scope, but it exceeds the typical 3-15 well-scoped range and approaches the 25+ threshold where agents may struggle to choose.

Completeness5/5

The tool surface is remarkably complete: full CRUD for notes (create, read, patch, replace, delete), task lifecycle (list, create, update), file reading, search (text, tag, folder, property), link graph (backlinks, outgoing, orphans), memory management (list, get, recall, update, delete), and daily notes. No obvious dead ends or missing operations for the stated vault management purpose.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    A server that enables AI agents to perform sophisticated knowledge discovery and analysis across Obsidian vaults through the Local REST API plugin, supporting complex multi-step workflows with advanced filtering and full content retrieval.
    3
    21
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A third-party MCP server for interacting with HashiCorp Vault to manage ACL policies, audit devices, and secret engines like KV v2, PKI, and Transit. It provides tools for system backend administration and includes prompts for generating security policy configurations.
    MIT