Replace Span
vault_replace_spanReplace a contiguous block of whole lines in an Obsidian note body by matching short start/end anchors, avoiding error-prone exact old-text reproduction for table rows, callouts, or list runs.
Instructions
Replace a contiguous block of whole lines in a note's body with new content, identified by short anchor substrings instead of the block's full text. Properties are kept as written, and body line breaks are saved as LF. A repeated call can write the content again, or with first_match: true replace the next match, so after a timeout or lost response, re-read the note before retrying.
Example: vault_replace_span({ path: "Tracker.md", start_anchor: "| 2024-03-02 | Acme", content: "| 2024-03-02 | Acme Corp | Updated |" }) — replaces the one table row whose line contains that fragment. Example: vault_replace_span({ path: "Notes/Plan.md", start_anchor: "> [!warning] Stale", end_anchor: "remove after launch", content: "> [!info] Current\n> Updated for v2." }) — replaces the callout block with a new one.
When to use: Replacing a block you have already read — a table row, callout, or run of list items — where reproducing it exactly as old_text would be error-prone. Prefer vault_replace_in_note for small in-place text changes (typos, renaming). Prefer vault_delete_span when removing without replacement.
Parameters:
An anchor matches a body line (any line below the properties block).
end_anchor is searched at or after the start line and must be unique among those lines. If start_anchor and end_anchor match the same line, only that one line is replaced.
A span that includes a code block's opening fence can't end at a plain closing fence (```): the end anchor is ambiguous, and first_match: true would end the span at the opening fence; to replace such a block, use vault_replace_in_note.
Behavior:
Each line break at content's start or end becomes an empty line on that side, so content ending in one ("| X |\n") leaves an empty line after it, which splits a table.
Where the note already has empty lines next to content's start or end, the wider run (the note's or content's) is kept, so blank lines never double. At the note's end, content's trailing line breaks count one fewer ("| X |\n" leaves no empty line).
Empty lines inside content are kept as written, and a line holding only spaces or tabs counts as text, not as an empty line.
Errors:
"note not found" — verify path with vault_list_notes
"path must end in …" — add the .md extension
"start anchor not found" / "end anchor not found" — no body line holds the fragment; verify with vault_read_note
"ambiguous start anchor …" / "ambiguous end anchor …" — the anchor matches multiple lines; use a longer fragment or set first_match: true
"absolute path blocked" / "path traversal blocked" / "hidden path blocked" — pass a vault-relative path with no dot-prefixed part
"concurrent write in progress" — a write to this note is in flight; re-read it and retry
"content contains a control character" — remove the character the message names
"ENAMETOOLONG: …" or another filesystem error — the message says what to do
"the note would open with a properties block …" — a note without properties would start with --- lines around text that can't be properties; the error says how to avoid it
Obsidian syntax: content is Obsidian Flavored Markdown (no escaping applied). Watch for: #word = tag, [[ = wikilink, %% = comment block.
Returns: Confirmation message "Replaced line(s) with line(s) in " — N counts the lines the span covered, M the lines in content as sent (its line breaks plus one).
Tool call template
Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"arguments": {
"path": "<path>",
"start_anchor": "<start_anchor>",
"content": "<content>"
},
"name": "vault_replace_span"
}
}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path to the note, including the ".md" extension (e.g. "Tracker.md", "Notes/Plan.md"). Use the exact letter case. | |
| content | Yes | Replacement content (one or more lines). | |
| end_anchor | No | Short substring of the block's LAST line, a body line (case-sensitive). Omit to replace just the single line containing start_anchor. | |
| first_match | No | If an anchor matches more than one line, use the first match instead of erroring (default: false — ambiguity is an error). | |
| start_anchor | Yes | Short, unique substring of the block's first line, a body line (case-sensitive). |