---
summary: "Every mutating tool now reports `previousSizeInBytes` + `currentSizeInBytes`; `obsidian_append_to_note` gains the `created` upsert flag. Resolves [#48](https://github.com/cyanheads/obsidian-mcp-server/issues/48)."
breaking: false
security: false
---

# 3.1.7 — 2026-05-10

`obsidian_append_to_note` is silently an upsert primitive — POST against a non-existent path creates the file and writes the body as its full content. This release rolls the response-transparency pattern from `obsidian_write_note` onto every mutating tool: each one now reports byte-size before and after the operation, and the upsert tools flag whether the write created the file. Agents get unambiguous evidence to spot typo paths, accidental whole-file clobbers, and concurrent-writer drift from the response alone.

## Added

- **`obsidian_append_to_note`** — `created: boolean` on the output. `true` flags the upsert branch where the agent's content became the entire file at a possibly unintended path; `format()` renders the same hint as `obsidian_write_note` when set. Always `false` for section appends (PATCH requires the file to exist).
- **Every mutating tool** — `previousSizeInBytes` + `currentSizeInBytes` on the output. Server reports raw before/after; agent computes deltas and decides what discrepancies mean (auto-newline injection, concurrent writers, accidental shrinkage). No server-side integrity verdict.
- **`ObsidianService.tryGetSize(ctx, target)`** — HEAD `/vault/{path}`, parses `Content-Length`, returns `null` on 404. Path-policy gated, retry-bypassed.
- **`ObsidianService.getSize(ctx, target)`** — same as `tryGetSize` but throws `note_missing` on 404. Used for post-write verification reads.

## Changed

- **`obsidian_delete_note`** — elicitation prompt now includes the byte size of the file being deleted (`Permanently delete '<path>' (<N> bytes)?`).
- **`obsidian_write_note`** overwrite check migrated from `noteExists` to `tryGetSize !== null`. Net wash on round trips, gain the `previousSizeInBytes` data for free on the same HEAD.
- **`obsidian_append_to_note` description** — front-loads the create-if-missing behavior of the no-section branch (matches the wording pattern `obsidian_write_note` uses for `overwrite`).
- **Source-of-truth rule for note byte sizes** documented on `tryGetSize`: prefer HEAD `Content-Length` or `Buffer.byteLength(deliveredContent)`, never `note.stat.size` from the JSON envelope (shares the upstream `getAbstractFileByPath` cache path with the rest of the envelope, so it can't act as an independent cross-check — see [coddingtonbear/obsidian-local-rest-api#237](https://github.com/coddingtonbear/obsidian-local-rest-api/issues/237)).
- **`@biomejs/biome` `^2.4.14` → `^2.4.15`** (devDependency).

## Removed

- **`ObsidianService.noteExists`** — sole caller (`obsidian_write_note` overwrite guard) migrated to `tryGetSize`.

## Per-tool source for `currentSizeInBytes`

| Tool | Source | Extra round trips |
|:---|:---|:---|
| `obsidian_write_note` | Post-write HEAD (whole-file) / pre+post HEAD (section) | +1 / +2 |
| `obsidian_append_to_note` | Post-write HEAD (whole-file) / pre+post HEAD (section) | +1 / +2 |
| `obsidian_patch_note` | Pre+post HEAD | +2 |
| `obsidian_delete_note` | Always 0 (file gone); pre-DELETE HEAD also feeds the elicit prompt | +1 |
| `obsidian_replace_in_note` | `Buffer.byteLength` from existing GET; post-write HEAD only when matches found | 0 / +1 |
| `obsidian_manage_frontmatter` (`set`) | `Buffer.byteLength(noteAfter.content)` from existing post-PATCH GET | +1 (pre-PATCH HEAD) |
| `obsidian_manage_frontmatter` (`delete`) | `Buffer.byteLength` from existing GET; post-write HEAD only when content changed | 0 / +1 |
| `obsidian_manage_tags` (`add` / `remove`) | `Buffer.byteLength` from existing pre/post GETs | 0 |
