0.0 / 30
What changed in the harness
Selection accuracy 96→92, token cost down 1%, unconfirmed writes 0%→0%.
Category breakdown
Where the score comes from.
Earned points across the four signals Gradable measures. Safety and Legibility are scored out of 30; Economics and Discoverability are scored out of 20.
01Safety
02Legibility
23.8 / 30
03Economics
12.8 / 20
04Discoverability
13.3 / 20
Highest-impact fix
Estimated gain +30 pointsAdd explicit identity and permission preflight tools
Expose machine-readable principal/tenant confirmation and a non-mutating permission check so agents can verify both before destructive actions.
Description evidence
Defects and rewrites.
11 defects found across the exposed tool descriptions. Suggested rewrites make purpose, inputs, boundaries, and returns easier for an agent to understand.
| Tool | Defect types | Suggested rewrite |
|---|---|---|
resolve_alias |
no_return_description |
Find every note whose frontmatter `aliases:` field contains the given name (case-insensitive), returning the paths of all matching notes. With `includeBasename: true`, also matches notes whose filename (without `.md`) equals the name - Obsidian's resolution fallback when no alias matches. Use to translate a human-friendly title like 'My Project' into the actual note path before calling get_note. |
create_note |
no_return_description |
Create a new markdown note at the given path with body content and optional YAML frontmatter, returning the created note's path. Fails (does not overwrite) if a note already exists at that path - use append_to_note, prepend_to_note, or update_frontmatter for existing notes. Missing directories are created automatically, and a .md extension is appended if omitted. |
append_to_note |
no_return_description |
Append text to the end of an existing note without altering prior content, returning the path of the updated note. By default, inserts a leading newline if the file does not already end in one, so appended content starts on its own line. Use for log entries, running lists, or adding new sections. Fails if the note does not exist - use create_note to make a new note first. |
prepend_to_note |
no_return_description |
Insert content at the top of an existing note's body, immediately after the YAML frontmatter block if one is present (so metadata stays at the top of the file), returning the path of the updated note. Use for adding new items to the front of a running list, pinning context, or inserting TL;DR sections. Fails if the note does not exist. |
create_daily_note |
no_return_description |
Create a daily note for today (or a specific date) in the vault's configured daily-note folder using its configured filename format, returning the path of the newly created daily note. Optionally seed the note from a template file with Obsidian-style placeholder substitution: {{date}} and {{title}} -> the formatted date; {{time}} -> local HH:mm; {{date:FORMAT}} / {{time:FORMAT}} -> custom moment-style format. Fails if the daily note already exists. |
move_note |
no_return_description |
Move or rename a note within the vault, preserving its full content, and return the note's new relative path. Parent folders at the destination are created as needed. By default, wikilinks and file references are updated, matching Obsidian's "Automatically update internal links" behavior; this rewrite requires `confirmPath` to match the destination path after .md normalization. Pass `updateLinks: false` to skip the rewrite scan (faster on large vaults; pair with `find_broken_links` if you need to audit afterward). A .md extension is added automatically if omitted from either path. |
delete_note |
no_return_description |
Delete a note, returning the path of the deleted file (in .trash or removed from disk). By default the file is moved to the vault's .trash folder (recoverable inside Obsidian); pass permanent=true to unlink it from disk immediately. When permanent=true, you can additionally pass removeReferences=true to strip wikilinks and markdown links to the deleted file across the vault (embeds are removed entirely; plain links fall back to their visible text). References are never rewritten when the file moves to .trash, since trashed files are recoverable. |
update_section |
no_return_description |
Replace the body of a specific section (everything between a heading and the next heading at any level), returning the updated note path. The heading line itself is preserved. `section` is a heading path: 'Tasks', 'Project A/Status', etc. - case-insensitive and whitespace-tolerant. Use this instead of rewriting the whole file when you only need to update one section. |
insert_at_section |
no_return_description |
Insert content into a specific section without replacing it, returning the updated note path. `position` controls where: 'before' inserts above the heading, 'after-heading' inserts immediately under the heading line (at the top of the section body), 'append' inserts at the end of the section's body just before the next heading. Use to add a new bullet or paragraph without rewriting the section. |
edit_block |
no_return_description |
Replace the content of a block tagged with `^id`, returning the updated note path. The trailing `^id` anchor is preserved on the last line of the new content so existing transclusions (`![[note#^id]]`) keep working. Use to update a single paragraph or list item that other notes reference. |
index_vault |
no_return_description |
Build or refresh the embedding index used by `search_semantic` and `find_similar_notes`, then return a summary of the run (e.g., counts of notes embedded, skipped via content-hash match, and any errors). Splits readable notes into heading-aware chunks, sends those chunks to the configured embedding provider (Ollama by default, OpenAI optional), and persists the index to `<vault>/.obsidian/cache/mcp-pro-embeddings.json`. Requires `confirm: "send-vault-text-to-embedding-provider"` so callers explicitly acknowledge that vault text will leave this tool boundary. Incremental: notes whose content hash matches the prior pass are skipped. Use `force: true` to re-embed everything (e.g., after switching models). Emits progress notifications when the client subscribes. |
Selection evidence
Confusable tool pairs.
13 pairs where similar names or overlapping descriptions may send an agent toward the wrong tool.
| Tool A | Tool B | Confidence | Why they collide |
|---|---|---|---|
create_note |
create_daily_note |
medium | Tasks like 'create a note for today' or 'make a note dated 2026-08-03' fit both: create_note takes an explicit path while create_daily_note resolves the configured daily-note folder/format, so an agent may pick the wrong one for date-oriented creation. |
get_note |
get_daily_note |
medium | 'Read today's note' or 'get the note for June 1' is ambiguous: get_daily_note takes a date and resolves via config, while get_note requires a path and could be called with a guessed date-based path instead. |
append_to_note |
prepend_to_note |
medium | Tasks like 'add this to my journal' or 'put this new entry in my note' don't specify position, and both tools insert content into an existing note, so the agent may pick either direction. |
get_backlinks |
get_outlinks |
medium | 'Show me the links for this note' or 'which notes are linked to X' is directionally ambiguous: backlinks are incoming references, outlinks are outgoing, and both surface wikilink relationships. |
search_notes |
search_semantic |
medium | 'Find notes about/related to onboarding' fits both: search_notes does literal full-text matching while search_semantic matches by meaning, so a natural-language query may route to the wrong search tool. |
search_notes |
search_by_frontmatter |
medium | The generic name search_notes is an attractor for any 'search for X in my vault' task, including metadata queries like 'search for status done', which should go to search_by_frontmatter. |
update_section |
insert_at_section |
medium | 'Put these items in the Tasks section' or 'update the Tasks section with this' is ambiguous between replacing the whole section body (update_section) and inserting into it (insert_at_section). |
find_orphans |
find_broken_links |
medium | 'Find broken/abandoned notes' or 'find problems with my vault's links' maps to both: orphans are disconnected notes while broken links are dangling references, and both are link-graph maintenance tasks. |
get_note |
get_recent_notes |
low | 'Get the latest note' or 'get what I recently worked on' could lead an agent to call get_note with a guessed path instead of get_recent_notes, though descriptions mostly disambiguate listing vs reading. |
search_notes |
search_by_tag |
low | 'Search my notes for #project' could be routed to full-text search_notes, which matches inline tags but misses frontmatter tags, instead of the tag-specific search_by_tag. |
update_frontmatter |
update_section |
low | 'Update the status of my note' is ambiguous because 'status' is a common frontmatter property AND a possible section heading, so the agent may target the wrong update tool. |
find_orphans |
find_unused_attachments |
low | 'Find unused/unreferenced content' fits both: orphans are notes with no links, unused attachments are assets with no references, so an agent may pick the wrong category for a hygiene task. |
read_base |
query_base |
low | 'Show me my base' or 'what's in my base' is ambiguous between returning the base file's definition (read_base) and running its filters against the vault (query_base). |
Compare the field