Skip to main content
Glama
maxkuminov

Obsidian MCP (pgvector + Ollama, self-hosted)

by maxkuminov

write_file

Save generated files, including PDFs or images, into the Obsidian vault with atomic, non-clobbering writes. Supports base64 or text content and optional overwrite with hash verification.

Instructions

Write a file into the vault — including non-markdown (e.g. save a generated PDF or image). Requires write permission — a readwrite API key, or an OAuth token carrying the readwrite scope. Peer to create_note/edit_note, which stay markdown-only.

content carries the bytes: with encoding="base64" (default) it is base64-decoded to raw bytes; with encoding="text" it is written verbatim as UTF-8. The write is atomic — the bytes are staged and flushed before anything is published — missing parent folders are created, and content over MAX_FILE_WRITE_BYTES (default 25 MB, decoded length) is refused.

No-clobber by default: writing over an existing file requires overwrite=True. The default publishes by linking a staged, never-named inode into place in one kernel-atomic step, so an existing file cannot be replaced; overwrite=True publishes with a single same-directory rename instead. A vault filesystem that cannot stage an unnamed file refuses the no-clobber write with an error naming VAULT_ALLOW_NAMED_STAGING_FALLBACK, rather than staging under a visible name. Any path with a component starting with . (dot-directories and dot-files alike) and path traversal are rejected; invalid base64 errors without writing anything. A path whose final component is a symlink is refused, naming its target, so overwrite=True cannot clobber a file through an alias; symlinked folders inside the vault work normally.

The MCP transport also bounds the whole request body (sized so a base64 write at the cap always gets through). Base64 is therefore the always-safe encoding: encoding="text" content whose JSON escaping inflates past that bound is rejected by the transport with a bare HTTP 413 before this tool runs — send such content as base64 instead.

Optional expected_hash binds an existing whole file on overwrite=True. Obtain its canonical sha256:<64 lowercase hex> value from read_file's base64 header, read_file(hash_only=True), or a prior write's success. Syntax is checked before path work; a hash with no-clobber or a missing destination is no_incumbent. An over-cap incumbent cannot be guarded. A stale hash refuses before publication; matching also enables the in-call comparison, which refuses an edit arriving during this call as concurrent_write. Without a hash, overwrite remains unconditional unless the deployment requires preconditions. Creation is exempt.

Success reports the hash of the bytes this call published, not necessarily what remains when the response arrives. Over-cap incumbents or results omit the hash without failing an otherwise permitted unguarded write.

Args: path: Vault-relative destination path (e.g. "Outputs/report.pdf"). content: File contents — base64 string (default) or UTF-8 text. encoding: "base64" (default) or "text". overwrite: If True, replace an existing file. Off by default. expected_hash: Optional whole-file raw-byte digest of the incumbent.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYes
contentYes
encodingNobase64
overwriteNo
expected_hashNo

Output Schema

TableJSON 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.4.0

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full behavioral burden. It discloses atomicity, no-clobber semantics, symlink handling, hash-based concurrency protection, and error conditions (refused symlinks, invalid base64, over-cap content). It also explains the transport 413 limit and the fallback staging error.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

While long, every sentence adds value. The structure is logical: purpose first, then encoding, atomicity, overwrite, hash semantics, and transport limits. No filler or repetition; it's dense but organized for an agent to parse.

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 complex write tool with no annotations, this is exceptionally complete. It covers all edge cases: encoding, overwrite, symlinks, dot-paths, hash concurrency, transport limits, and staging fallback. The output schema presumably covers the return value, so that gap 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 must explain each parameter. It does: path (vault-relative), content (base64/text), encoding (base64 default), overwrite (replaces existing), expected_hash (binds incumbent). It adds crucial meaning beyond the schema, including encoding format details and hash acquisition methods.

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 description opens with 'Write a file into the vault — including non-markdown' which is a specific verb+resource+scope. It explicitly distinguishes itself from siblings: 'Peer to create_note/edit_note, which stay markdown-only.' An agent can immediately tell it apart from note tools.

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?

It names the alternatives (create_note/edit_note) and the condition that selects them (markdown-only). It also gives explicit encoding guidance (base64 vs text) and explains when overwrite is required (no-clobber by default). The transport limit note clarifies when to use base64.

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