Skip to main content
Glama
maxkuminov

Obsidian MCP (pgvector + Ollama, self-hosted)

by maxkuminov

move_note

Moves or renames a note in the vault and updates wikilinks/backlinks to the new path, with optional link rewriting and hash-based safety checks to prevent stale writes.

Instructions

Move or rename a note inside the vault. Requires write permission — a readwrite API key, or an OAuth token carrying the readwrite scope.

Updates notes_metadata.file_path for the moved note and note_links.target_path rows whose stored target matched the old path. Backlinks via target_note_id keep working without rewriting source notes (the moved note's id is unchanged).

With rewrite_links=True, also opens every source note that linked to this note and rewrites the link in place: [[Old]] → [[New]], [[Old|alias]] → [[New|alias]], [[Old#anchor]] → [[New#anchor]], ![[Old]] → ![[New]], path-style [[folder/Old]] → [[new/folder/New]], and markdown links [text](Old.md) → [text](<new path>), whose href is written relative to the linking note's own folder (anchors preserved). Aliases and anchors survive; only the target portion is rewritten. The moved note's own body is rewritten as well, so a self-reference does not end up pointing at the old path.

The rewrites are planned before anything changes: if one would push a source note past the 10 MiB note limit the whole move is refused, naming that source, before any file is touched. That preflight is also bounded in aggregate: if the originals plus rewrites for all backlink sources would exceed 256 MiB in memory the move is refused before anything changes, naming the note count and the limit.

The same preflight refuses the whole move, before the rename, when any source it would rewrite — the moved note's own body included — contains a fence opener indented by one to three spaces that nothing below it closes. The refusal names each such source and where its opener sits. A link under such an opener may be inside a list item's code block, which this server does not parse, and a rewrite would mutate text whose code-or-content status had to be guessed. Move with rewrite_links=False (unaffected by this refusal) and fix the links yourself, or close the fences first.

A rewrite can still fail after the move has committed. The move is one rename and the rewrites follow it, so an I/O failure, a vault reassignment, or a database that cannot be reached to confirm the assignment stops the remaining rewrites — and the result then reads partial success: …, naming the sources that still link to the old path. The move is not rolled back and the index rows describe where the note now is. Treat the link graph as agreeing with the vault bytes only when the result reports plain success; on a partial outcome, fix the named sources with edit_note.

Writes are atomic. Either path is refused, naming the link's target, when its final component is a symlink; symlinked folders inside the vault work normally and the recorded paths are the real ones behind them. See get_vault_guide for vault folder conventions.

expected_hash — binding a write to the bytes you read. Optional; omit it and nothing changes. Pass the content_hash a read returned, verbatim and canonical (sha256:<64 lowercase hex>), and the call is refused with nothing written if the file changed in between. It is always the whole file's hash, never a hash of the text you received. expected_hash covers your read through this call's preflight read. The rename pins inode identity but does not compare the bytes again: an in-place edit after that comparison can still be moved. Each optional link rewrite retains its own pre-publication byte comparison. Precondition refusals end with one machine-readable MCP-REFUSAL {"code":…} line — stale_precondition (with the file's current hash, ready to resend), concurrent_write, no_incumbent, malformed_precondition, precondition_unavailable, precondition_required — and each states what resolves it.

Here it binds from_path's own bytes and nothing else, compared before the rename and before any rewrite; the backlink sources a rewrite_links=True move would rewrite are not bound, because you never read them. Success reports the content_hash of the bytes actually published at to_path — the moved bytes for a plain move, the post-rewrite bytes when the moved note's own body was rewritten — and that value binds a following edit_note(to_path, …, expected_hash=…). A rewrite that fails after the rename stays a partial success and never claims nothing was written; if it failed because another writer changed to_path in between, no hash is reported at all and the result says to re-read to_path before writing to it.

Args: from_path: Vault-relative path of the existing note. to_path: Vault-relative path of the destination. Must not exist. Parent directories are created automatically. rewrite_links: If True, also rewrite incoming wikilinks and embeds in source notes. Off by default — opting in is destructive (it modifies other notes' bodies). expected_hash: from_path's content_hash as you last read it. Refuses the move, changing nothing, if that note has changed since.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
to_pathYes
from_pathYes
expected_hashNo
rewrite_linksNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changedv0.8.2
    • addedInput schema / additionalProperties
      Added value: +false
  2. Changed1 schema field changedv0.8.1
    • addedInput schema / properties / expected_hash
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Expected Hash"
      +}
  3. Addedv0.7.0
  4. Removedv0.5.4
  5. First observed

TDQS

A4.7/5.0
Behavior5/5

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

With no annotations provided, the description carries the full behavioral disclosure burden, and it does so thoroughly. It discloses metadata and link-table updates, preflight failure modes for note-size and fence conditions, partial-success behavior after the move commits, 'not rolled back' semantics, atomic writes, symlink handling, and the exact behavior of expected_hash including refusal codes and returned content_hash.

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

Conciseness4/5

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

The description is front-loaded with a one-sentence summary and uses bold section headers, making a long and complex behavior navigable. It is not padded, though the expected_hash narrative somewhat restates the Args bullet and the overall length is substantial. The structure earns a high score rather than a maximum.

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

Completeness5/5

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

For a mutating tool with no annotations, complex preconditions, and possible partial failures, the description includes everything needed for correct invocation: permissions, preflight conditions, refusal codes, partial-success handling, symlink behavior, and hash chaining to a subsequent edit_note. An output schema exists, so return-value documentation is not required here.

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 description coverage is 0%, but the description's Args section defines all four parameters with meaning far beyond the schema names. It explains that to_path must not exist and parent directories are created automatically, that rewrite_links is destructive and off by default, and the canonical form and precondition behavior of expected_hash.

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 states a specific action and resource: 'Move or rename a note inside the vault.' This clearly distinguishes it from content-editing, creating, or deleting siblings. The rest of the description reinforces that it is about changing a note's path, not its content.

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?

The description gives explicit alternatives and exclusions for edge cases: 'Move with rewrite_links=False ... fix the links yourself, or close the fences first,' 'fix the named sources with edit_note,' and 'See get_vault_guide for vault folder conventions.' It also states the write-permission requirement. It does not, however, provide a general when-to-use versus content-editing alternatives statement, so it stops short of a 5.

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