Skip to main content
Glama
Obsidian MCP (pgvector + Ollama, self-hosted)

Obsidian MCP (pgvector + Ollama, self-hosted)

by maxkuminov

Set frontmatter

set_frontmatter
Destructive

Update a note's YAML frontmatter keys without touching its body, optionally refusing stale writes via expected_hash to prevent overwriting concurrent edits.

Instructions

Mutate a note's YAML frontmatter without touching its body. Requires write permission — a readwrite API key, or an OAuth token carrying the readwrite scope.

Arguments: path, updates (keys to set), remove (keys to drop) and the optional expected_hash precondition. Parses the existing frontmatter, merges in updates (overwriting matching keys, adding new ones), then drops the keys in remove; the body is preserved byte-for-byte. A note with no frontmatter (no --- fence on line 1) gets a fresh block prepended ahead of the unchanged body.

expected_hash: pass the content_hash a read returned, verbatim (sha256:<64 lowercase hex>), and the call is refused with nothing written if the file changed since. It is the whole file's hash, never a hash of the text you received. Optional by default — omitted, a stale edit overwrites — unless the deployment requires it, when an omitted hash is refused precondition_required. The comparison runs ahead of the malformed-block diagnosis and ahead of the no-op check, so a stale base never yields "no changes" or a defect report about bytes you have not seen. Every refusal the server raises ends with one machine-readable MCP-REFUSAL {"code":…} line (an undeclared or mistyped argument is rejected by protocol validation before the tool runs and carries no such line) — stale_precondition (with the current hash), concurrent_write, no_incumbent, malformed_precondition, precondition_unavailable, precondition_required — each stating what resolves it. The server also re-compares the file immediately before publishing. A write reports the content_hash of the bytes this call published; a no-op publishes nothing and reports none.

A malformed block is refused, never worked around. An unclosed line-1 fence, YAML that fails to parse, and YAML that is not a mapping (null, ~, comments only, a list, a scalar) each return an error naming the defect and pointing at edit_note(path, content, replace_frontmatter=True) as the repair. Nothing is written — in particular no second block is prepended above the broken one — and remove= refuses identically rather than silently doing nothing, even for a call with no updates and no remove. An empty fenced block (--- immediately followed by ---) is valid: it is a valid empty mapping and is updated in place.

Only an effective change writes. updates that set every named key to the value it already holds (compared type-sensitively, so true is not 1) together with remove naming only absent keys report no changes and leave the file byte-identical. Removing the last key removes the block entirely — no fences, no separator, exactly the prior body.

Re-serialization goes through PyYAML, which does NOT preserve YAML comments — any # comment in the original frontmatter is lost on the first set_frontmatter call. A path whose final component is a symlink is refused, naming its target; symlinked folders inside the vault work normally. See get_vault_guide for vault frontmatter conventions.

Args: path: Vault-relative path to the note. updates: Mapping of keys to set. Use the empty dict (or omit) to skip. remove: List of keys to delete from the frontmatter. Missing keys are ignored (and, on their own, make the call a no-op rather than a write). expected_hash: The note's content_hash as you last read it. Refuses the write, changing nothing, if the note has changed since.

Input Schema

NameRequiredDescriptionDefault
pathYes
removeNo
updatesNo
expected_hashNo

Output 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?

Despite annotations already declaring destructive/non-readonly, the description goes well beyond them: required scope (`readwrite` key or scope), the full refusal taxonomy with machine-readable codes, ordering of precondition checks relative to no-op and malformed-block diagnosis, comment loss through PyYAML, byte-for-byte body preservation, symlink refusal, and the exact no-op rule. This is precisely the kind of concrete behavioral context the annotations cannot convey.

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 critical information — what it mutates, permission requirement, and no-body-change guarantee — is front-loaded, and the bolded sections make skimming feasible for a genuinely complex tool. It is long, though, and the trailing Args block partly restates the `expected_hash` and `remove` prose already given above, which is mild duplication rather than dead weight.

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 mutation tool with four parameters, an output schema, and hash-precondition semantics, everything an agent needs is present: auth requirements, error/refusal behavior and resolution, edge cases (empty fence valid, unclosed fence refused, last key removed drops the block), and operational caveats (comment loss, symlinks). Nothing material is deferred to guesswork.

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 carries the full burden and does: `updates` is a key→value mapping that overwrites and adds, `remove` ignores absent keys (and alone makes a no-op), and `expected_hash` must be the whole-file `sha256:<64 lowercase hex>` from a prior read, not a hash of received text. Value comparison is even specified as type-sensitive (`true` is not `1`).

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 gives a specific verb and resource ('Mutate a note's YAML frontmatter') plus the key scope constraint ('without touching its body'), which immediately separates it from write_file, edit_note, and create_note. An agent can select this tool without opening the schema.

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?

It routes the agent elsewhere for the malformed-block case by naming `edit_note(path, content, replace_frontmatter=True)` as the repair path, and points to `get_vault_guide` for conventions. It also states when `expected_hash` should and shouldn't be supplied. What's missing is an explicit statement of when to prefer this over edit_note/write_file for ordinary edits, so it falls short of a full when/when-not/alternatives treatment.

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