- create_vault_directory: create directories in a vault (with parents=True).
Idempotent — returns 'exists' if directory already present; errors
if the path exists but isn't a directory.
- move_document: move or rename a document within a vault. Creates parent
directories at destination. Errors if src missing, src isn't a file,
or dest already exists.
Excludes .webp, .png, .jpg, .gif, .svg, .pdf, .mp4, .mp3, .mov,
.excalidraw, .bmp, .ico, .tiff, .heic, .avif — the model can't
read these anyway and they were the remaining bulk of the 53K
payload after the .obsidian purge.
New exclude_extensions param with sensible default blacklist.
Pass exclude_extensions=[] to see everything.
The helper was hardcoding '.obsidian != .obsidian = False' so
_is_hidden_path never returned True for .obsidian paths. The
include_hidden parameter in list_vault was supposed to be the sole
gate. Now ALL dot-prefixed paths are hidden by default; set
include_hidden=True to peek at .obsidian config.
list_vault: Each entry now ~55 chars (name+path+type) vs ~120 before.
- Dropped size_bytes (was 0 for dirs, wasted tokens)
- Dropped modified date (moved to get_file_info)
- .obsidian hidden by default (include_hidden=True to opt in)
- Uses _file_info_lean helper
New get_file_info tool: Full metadata for a single file.
- size_bytes, modified (YYYY-MM-DD), line_count, char_count
All dates compacted: '2025-06-10' vs '2025-06-10T23:40:11.798359+00:00'
(22 chars saved per date, ~10x token savings)
Added shared helpers:
- _compact_date() for YYYY-MM-DD everywhere
- _is_hidden_path() replacing 4 duplicate filter blocks
Estimated payload reduction for 311-note vault: ~55% on files,
plus elimination of entire .obsidian config subtree.
Models kept passing the vault name as both vault_name AND path
(e.g. vault_name='obsidian-skt', path='obsidian-skt'), producing garbage
results. The old description 'Relative path within the vault' was too
vague — models interpreted path as the vault identifier.
- path: now explicitly says 'NOT the vault name — that's vault_name'
- Added concrete Usage section with 4 examples
- Described as 'Subfolder inside the vault' not 'Relative path'
rglob walks into .git/ before the name-check on .git itself fires.
Files under .git/objects/ have normal names so the entry-level filter
didn't stop them. Added ancestor-path check that skips any entry with
a hidden (dot-prefixed) directory anywhere in its path.
The result was a 566K-char blob that blew past the model's 32K context.
Ulysses truncates MCP tool descriptions to 120 chars in prompt text.
Moved '(not memory)' to first line of all four tool docstrings so the
disambiguation survives truncation. Kept Args/Returns sections intact.
Qwen3.5-9B was conflating these tools with manage_memory because the
descriptions sounded similar ('list vault contents' vs 'search memory').
Every tool now opens with 'FILESYSTEM TOOL' to create a hard routing
boundary from conversation/memory tools. Added explicit NOT clauses
('NOT a memory search, NOT a conversation search') and USE THIS TO
guidance so the model routes correctly on first attempt.