Skip to main content
Glama
maxkuminov

Obsidian MCP (pgvector + Ollama, self-hosted)

by maxkuminov

edit_note

Modify existing notes in an Obsidian vault using full replacement, append, find-and-replace, or section-body edits, preserving frontmatter and writing atomically.

Instructions

Edit an existing note in the Obsidian vault. Requires write permission — a readwrite API key, or an OAuth token carrying the readwrite scope.

See get_vault_guide for Obsidian syntax and any vault-specific conventions (naming, folder placement, frontmatter, tags).

Four mutually exclusive modes (set at most one of append/find/section):

  1. Full replace (default): provide only content. content becomes the note's body; an existing valid line-1 YAML frontmatter block is preserved byte-identically ahead of it. Pass replace_frontmatter=True to overwrite the entire file, frontmatter included.

  2. Append: append=True; content is added at the end (preceded by a single newline).

  3. Find & replace: find=<exact text>; replaced with content. Must match exactly once unless replace_all=True. This mode operates on the raw file, so it is the one mode that can edit frontmatter text in place.

  4. Section: section=<heading>; replaces the whole body under the named ATX heading — see "Section mode: what content replaces" below. Use the path-style form Parent/Child to disambiguate when the same heading appears more than once, or the #N ordinal form ("#7", 1-based document order) — the ordinal is the only form that can address duplicate headings sharing one parent, and it is the selector the outline of a truncated read_note advertises. A bare "#N" always selects by position and is never shadowed by a heading whose text happens to be "#N"; reach such a heading by title with "Parent/#N". A selector resolves to the same section in read_note as in this tool on any write this tool admits — that parity is about resolution, not about admission: see the two section-mode refusals below, where a section that reads fine is deliberately not writable. Setext (====/----) headings are not matched.

Frontmatter and the round trip. Read a note, edit the content field of the response, pass it straight back to full replacement: the frontmatter survives. No property of content's shape changes that — a body whose first line is a thematic break ---, or which itself begins with a complete mapping-shaped fenced block, is body. A note with no valid block (no line-1 fence, or a malformed one) is replaced wholesale by default, which is the repair path and needs no flag. To change the frontmatter itself use set_frontmatter, or edit the raw block through find=; a read_note response's frontmatter JSON view is a lossy convenience and must never be written back.

The round-trip guarantee covers a complete, unwindowed whole-note read onlyread_note(path) with no section, offset=0 and truncated false in the response. A truncated read must be paged to the end before it is written back, or full replacement will replace the whole body with the fragment.

Section mode: what content replaces.

  • In section mode content is the section's body: the text beginning on the line immediately after the matched heading line, running to the next heading of equal-or-shallower depth or to end of note. The heading line itself is never removed or rewritten.

  • A section write replaces that body whole. Anything content does not resend is deleted — a blank line, a list, and a fenced code block sitting directly under the heading included. There is no third region between the heading line and the body that survives a write.

  • So a blank line you want between the heading and its content belongs in content (send "\ntext", not "text").

  • read_note(path, section=...) is the matching read: its response carries the heading line in the heading field and the body in the content field, and this tool takes exactly that content. Pass the field through unchanged — there is nothing to split off and nothing to strip.

  • Byte-identity holds for notes whose body newlines are LF. Every non-LF terminator inside the selected body (CRLF, or a lone CR) comes back as LF — the read path normalises and this tool writes raw bytes — whether the note uses one dialect throughout or mixes them. Terminators outside the selected body are untouched, so a round trip can leave a note with more mixed endings than it started with.

Section mode resolves headings over the frontmatter-stripped body, exactly as read_note does, so #N ordinals agree between the two and a YAML # comment inside the block is never selectable. A heading inside a fenced code block is not a heading: fences count with up to three spaces of indentation and a closer at least as long as the opener, and an unclosed column-zero fence hides everything below it.

Two shapes refuse a section write outright, naming the problem and writing nothing:

  • a malformed frontmatter block (unclosed fence, YAML error, non-mapping) — the refusal names the defect and the replace_frontmatter=True repair;

  • a fence opener indented by one to three spaces that nothing below it closes — such an opener may sit inside a list item, whose code block ends where the item does, and this server does not parse container blocks, so it will not guess whether the text below is code or content. Close the fence or unindent it to column zero, then reissue.

Both refusals are asymmetric with reads on purpose: read_note(section=…) and the truncation outline keep working on such notes, because a read destroys nothing.

Flags:

  • operation="append": legacy alias for append=True. This is accepted to prevent older clients from silently falling through to full replacement. operation="replace" explicitly selects full replacement.

  • replace_all=True: with find, replace every occurrence rather than failing on multiple matches. Ignored when find is unset.

  • replace_frontmatter=True: full replacement overwrites the entire file including the frontmatter block. Combined with append/find/section it is an error and nothing is written.

  • dry_run=True: compute the would-be result and return a unified diff without writing. Works for all four modes, and diffs the composed result.

Writes are atomic: the composed result is staged in the note's own directory, flushed to disk, and published with a single same-directory rename, so a crash mid-write cannot truncate the destination. The publish is optimistic, not locked — the bytes this call read are compared against the file immediately before that rename, so a note somebody else changed in the meantime fails with File changed while editing: <name> and nothing is written; re-read and retry. Structured frontmatter mutation is better done via set_frontmatter — PyYAML serialization there discards YAML comments. A path whose final component is a symlink is refused in every mode (dry_run included), naming the link's target; symlinked folders inside the vault work normally.

Args: path: Vault-relative path to the note. content: New body (full replace), replacement text, text to append, or section body. append: If True, append content to the end of the note. operation: Legacy mode selector; accepts "append" or "replace". find: Exact text to find and replace. section: ATX heading text identifying the section whose body to replace. Use Parent/Child to disambiguate repeated headings, or a "#N" ordinal ("#7", 1-based document order) for duplicate siblings. replace_all: With find, replace every match instead of requiring uniqueness. dry_run: Return a unified diff and do not write. replace_frontmatter: Full-replace only. If True, content replaces the entire file including any frontmatter block. Default False preserves an existing valid block.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
findNo
pathYes
appendNo
contentYes
dry_runNo
sectionNo
operationNo
replace_allNo
replace_frontmatterNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes
Install Server

TDQS

A4.9/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 burden and does so thoroughly. It discloses write-permission requirements, frontmatter byte-identity preservation, atomic same-directory rename semantics, optimistic concurrency checks with 'File changed while editing', dry_run diff behavior, and refusal conditions.

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 exceptionally detailed and well-structured with bolded section headers and front-loaded purpose, but it is very long and repeats some information in the mode list, section-mode deep dive, and Args list. It earns its length overall, but is not maximally 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 nine-parameter mutation tool with no annotations and no schema-level parameter descriptions, the definition is complete. It covers every parameter, mode, failure case, concurrency behavior, and round-trip guarantee; since an output schema exists, the absence of return-value prose is acceptable.

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%, so the description fully compensates. The Args section explains all nine parameters in prose, including operation's legacy 'append'/'replace' values, section selector forms like 'Parent/Child' and '#N', replace_frontmatter's default behavior, and find uniqueness semantics with replace_all.

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?

Opens with a specific verb and resource: 'Edit an existing note in the Obsidian vault.' It then enumerates four mutually exclusive modes, which clearly differentiates it from siblings like write_file and create_note, and names set_frontmatter for frontmatter-specific mutation.

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 guidance: it points to get_vault_guide for conventions, names set_frontmatter as the better tool for structured frontmatter mutation, explains that a truncated read must be paged before writing back, and documents explicit refusals such as malformed frontmatter and indented unclosed fences.

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

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/maxkuminov/obsidian-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server