Set frontmatter
set_frontmatterUpdate 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
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| remove | No | ||
| updates | No | ||
| expected_hash | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |