Skip to content

Writer

Every tool on this page changes the vault, so a server with MARKDOWN_VAULT_MCP_READ_ONLY=true lists none of them; the security model says what they reach. The okf_* tools also need Open Knowledge Format support on, and okf_verify needs OKF writes on.

A write saves the file and queues the index update. A following read sees the new file, while search and graph reads may lag behind until the index catches up: pass wait_for_pending_writes=true on the read tools that take it, and check _meta.index_stale. No reindex is needed.

write

Write Note. Changes state, not destructive. Idempotent. Tags: write.

Create a note or attachment, or replace one whole; returns its path and whether it was created.

Use edit for targeted changes and append to add to the end. Parent folders are created as needed.

Parameters

Name Type Default Description
path string required Path in the vault, such as "Journal/note.md" or "assets/photo.png"; the extension decides between note and attachment.
content string "" The note's Markdown body, without frontmatter.
frontmatter object \| null null The note's frontmatter, such as {"title": "My Note", "tags": ["draft"]}.
content_base64 string "" An attachment's bytes, base64-encoded; for paths not ending in .md.
if_match string \| null null Etag from read, to replace an existing file; omit for a new file.

Returns

Dict with path (str) and created (bool: true if new file, false if overwrite). On a git-backed vault, an overwrite also carries 'previous_revision': the commit holding the content this write just replaced. Read it back with read(path, revision=), then write it again with if_match set to the etag from a plain read(path). The key is absent when no commit provably holds the replaced content: a create, no git, or content that was never committed (which git cannot recover at all). For .md files, may include 'conventions': the user's authoring conventions for the target folder (root-first list of {folder, path, content}). When present, verify the note you just wrote complies, such as with self-containment or linking-direction rules, and issue a follow-up 'edit' if it does not. To check conventions before writing, call 'get_conventions(path)'.

  • unresolved_links (list[str] or null, optional): present only for a .md path, when the server runs with MARKDOWN_VAULT_MCP_REPORT_UNRESOLVED_LINKS=true. The target, as written, of each link this write added that get_broken_links reports; links the note already held are left out. An empty list means every new link resolves; null means the index could not be checked this time.
  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

Outcomes and errors

  • ValueError: If content_base64 is missing/invalid for attachments, or the content exceeds MARKDOWN_VAULT_MCP_MAX_ATTACHMENT_SIZE_MB.
  • MCPError: If if_match is provided and the file has been modified, or if_match is supplied for a file that does not yet exist (ConcurrentModificationError). Also when the server runs with MARKDOWN_VAULT_MCP_WRITE_PROTECT_EXISTING=true (default) and path already exists while no if_match is supplied (DocumentExistsError); use 'edit' or 'append' instead, or read the file first and pass its etag as if_match.

edit

Edit Note. Changes state, not destructive. Tags: write.

Replace text in an existing note, chosen by an exact old_text, by a range of lines, or by an old_text within a range of lines.

Read the note first for its current text. When old_text has no exact match, a unique match after normalising Unicode, dashes, quotes and whitespace is used and reported as match_type "normalized".

Parameters

Name Type Default Description
path string required Path of the note.
old_text string \| null null Text to replace, occurring once in the note or in the line range; omit to replace the whole line range.
new_text string "" Replacement text; empty deletes the matched text or the lines.
if_match string \| null null Etag from read. Pass it, one edit per read, when line_start and line_end are given; omit it for several old_text-only edits to one note at once.
line_start integer \| null null First line to replace, counting from 1 over the content read returns, frontmatter included; pass with line_end.
line_end integer \| null null Last line to replace, inclusive; pass with line_start.

Returns

  • path (str): path of the edited document.
  • replacements (int): always 1.
  • match_type (str): 'exact' or 'normalized'.
  • conventions (list, optional): the user's authoring conventions for the note's folder (root-first list of {folder, path, content}). When present, verify the edited note complies and issue a follow-up 'edit' if it does not.
  • unresolved_links (list[str] or null, optional): present only when the server runs with MARKDOWN_VAULT_MCP_REPORT_UNRESOLVED_LINKS=true. The target, as written, of each link this edit added that get_broken_links reports; links the note already held are left out. An empty list means every new link resolves; null means the index could not be checked this time.
  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

Outcomes and errors

  • ValueError: If parameter combination is invalid, or line numbers are out of range.
  • EditConflictError: If old_text is not found or appears more than once; a not-found error may name the closest line and the first differing character.
  • DocumentNotFoundError: If no file exists at the given path.
  • MCPError: If if_match is provided and the file has been modified (ConcurrentModificationError).

append

Append to Note. Changes state, not destructive. Tags: write.

Add text to the end of an existing note without reading it first.

Prefer it over edit when the change only adds at the end, such as a log entry. The text starts on a new line; begin it with a blank line or a heading to start a paragraph or section.

Parameters

Name Type Default Description
path string required Path of the note, such as "Journal/2026.md".
content string required Non-empty text to add after everything the note holds.
if_match string \| null null Etag from read; the text is then added only to that version. Omit to append regardless.
create_if_missing boolean false Create the note with content as its body when path does not exist. Default false, so a mistyped path creates nothing.

Returns

  • path (str): path of the document.
  • created (bool): true only when create_if_missing created a new note.
  • conventions (list, optional): the user's authoring conventions for the note's folder (root-first list of {folder, path, content}). When present, verify the appended content complies and issue a follow-up 'edit' if it does not.
  • unresolved_links (list[str] or null, optional): present only when the server runs with MARKDOWN_VAULT_MCP_REPORT_UNRESOLVED_LINKS=true. The target, as written, of each link this append added that get_broken_links reports; links the note already held are left out. An empty list means every new link resolves; null means the index could not be checked this time.
  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

Outcomes and errors

  • ValueError: If content is empty.
  • DocumentNotFoundError: If no file exists at the given path and create_if_missing is false.
  • MCPError: If if_match is provided and the file has been modified (ConcurrentModificationError).

delete

Delete Note. Destructive. Idempotent. Tags: write.

Permanently delete a note or attachment; only git history can bring it back.

Parameters

Name Type Default Description
path string required Path of the note or attachment.
if_match string \| null null Etag from read; the file is then deleted only as that version. Omit to delete it as it is.

Returns

Dict with path (str) of the deleted file.

  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

Outcomes and errors

  • DocumentNotFoundError: If no file exists at the given path.
  • MCPError: If if_match is provided and the file has been modified (ConcurrentModificationError).

rename

Rename Note. Changes state, not destructive. Tags: write.

Rename or move a note or attachment; with update_links, links to it in other notes follow it.

For a note, always pass update_links=True. Parent folders are created as needed.

Parameters

Name Type Default Description
old_path string required Current path, such as "drafts/idea.md" or "assets/old.png".
new_path string required New path, such as "projects/idea.md", where nothing exists yet.
if_match string \| null null Etag from read of old_path; omit when renaming several linked notes together.
update_links boolean false Rewrite links to old_path in other notes so they point at new_path; attachment references are not tracked. Default false; pass true for every note.

Returns

Dict with old_path (str), new_path (str), and updated_links (int) counting the number of source documents whose links were updated. Carries hint (str) only when update_links was requested for an attachment, saying why nothing was rewritten.

  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

Outcomes and errors

  • DocumentNotFoundError: If old_path does not exist.
  • DocumentExistsError: If new_path already exists.
  • ValueError: If the path fails traversal validation.
  • MCPError: If if_match is provided and the file has been modified (ConcurrentModificationError).
  • TimeoutError: With update_links on a note, if writes queued before the call have not reached the index within 60 seconds; nothing is changed.
  • IndexUnavailableError: With update_links on a note, if the index build failed; nothing is changed.

move_folder

Move Folder. Destructive. Tags: write.

Move a folder and everything under it to a new location, rewriting every link into it across the vault: rename for a whole folder.

Notes whose links could not be rewritten are listed in failed_links.

Parameters

Name Type Default Description
old_dir string required Folder to move, such as "drafts".
new_dir string required Destination folder, such as "archive/2026"; an existing folder is merged into.

Returns

Dict with old_dir (str), new_dir (str), files_moved (int) and updated_links (int), plus:

  • failed_links (list[str]): paths of notes whose links could not be rewritten; the move itself stands.
  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

Outcomes and errors

  • DocumentNotFoundError: If no non-empty folder exists at old_dir.
  • DocumentExistsError: If any destination file already exists; nothing is moved.
  • ValueError: If a path fails traversal validation or the two paths are nested.
  • ToolError: If a file error on the server interrupts the move, so some files may already be under new_dir; call reindex so search matches the files.
  • TimeoutError: If writes queued before the call have not reached the index within 60 seconds; nothing is changed.
  • IndexUnavailableError: If the index build failed; nothing is changed.

fetch

Fetch to Vault. Changes state, not destructive. Idempotent. Tags: write.

Download a URL on the server and save it in the vault as a note or attachment; returns its path, size and final_url.

The bytes never pass through the conversation, so use it for large files and then pass the path to other tools.

Parameters

Name Type Default Description
url string required An HTTP or HTTPS URL on the public internet. Redirects are followed, so check final_url in the result when the source host matters.
path string required Destination path, such as "notes/report.md" or "assets/diagram.png"; .md saves a note, anything else an attachment.
frontmatter object \| null null Frontmatter for a note, such as {"title": "Report", "source": "https://example.org/report"}.
if_match string \| null null Etag from read, to replace an existing file; omit for a new file.
timeout_s number 30.0 Download timeout in seconds (default 30); raise it for a large file from a slow host.

Returns

Dict with:

  • path (str): vault path of the written file
  • created (bool): true if new file, false if overwrite
  • content_length (int): bytes downloaded
  • content_type (str or null): Content-Type from the response
  • final_url (str): the URL the bytes actually came from: equal to url when nothing redirected, otherwise the last hop. A user name or password in the URL is stripped; the query string is not, so do not log it verbatim
  • conventions (list, optional; .md only): the user's authoring conventions for the target folder (root-first list of {folder, path, content}). When present, verify the saved note complies and issue a follow-up 'edit' if it does not.
  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

Outcomes and errors

  • DocumentExistsError: If the server runs with MARKDOWN_VAULT_MCP_WRITE_PROTECT_EXISTING=true (default) and path already exists while no if_match is supplied. The save routes through the same guarded write / write_attachment path as the write tools, so read the existing note first and pass its etag as if_match to replace it deliberately, or fetch to a fresh path.
  • InvalidRequestError: If the URL scheme is not http/https, the host is not publicly routable (private, loopback, link-local, carrier-grade NAT and reserved ranges are refused) or cannot be resolved, checked on the supplied URL and on every redirect hop, the download exceeds the size limit, or the response cannot be decoded.
  • ToolError: If the remote site answers a non-2xx status (retry later for a 5xx, check the URL otherwise; the message never names the URL) or redirects more times than the client allows.
  • httpx.HTTPStatusError: On a 429; tool_boundary turns it into a "retry later" answer.
  • httpx.TransportError: On a timeout or a failed connection; it propagates to tool_boundary.

fetch(url="https://example.com/report", path="notes/report.md") returns

{"path": "notes/report.md", "created": true, "content_length": 4096, "content_type": "text/markdown", "final_url": "https://example.com/report.md"}

final_url differs from url because the site redirected.

OKF: Convert Wikilinks. Destructive. Idempotent. Tags: okf, write.

Rewrite wikilinks as bundle-root-absolute Markdown links, the Open Knowledge Format's recommended style; returns counts of links converted and skipped.

A wikilink whose target is not in the vault stays as it is. Running it again changes nothing already converted.

Parameters

Name Type Default Description
folder string \| null null Only notes in this folder and below, such as "guides"; omit for the whole vault.

Returns

Dict with:

  • files_changed (int): notes rewritten.
  • links_converted (int): wikilinks turned into Markdown links.
  • links_skipped (int): wikilinks left as they are because the target is not in the vault.
  • notes_scanned (int): notes examined.
  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

An attachment embed such as ![[pic.png]] is not a link, so it is left as it is and not counted.

Outcomes and errors

  • TimeoutError: If writes queued before the call have not reached the index within 60 seconds; nothing is changed.
  • IndexUnavailableError: If the index build failed; nothing is changed.
  • DocumentUnreadableError: If a note it rewrites cannot be read; notes converted before it keep their changes.

okf_convert_links(folder="guides") on a note containing

See [[Project Notes/plan|the plan]] and [[Missing note]].

rewrites it to

See [the plan](/Project%20Notes/plan.md) and [[Missing note]].

Missing note has no target in the vault, so it stays and counts in links_skipped.

okf_generate_index

OKF: Generate index.md. Destructive. Idempotent. Tags: okf, write.

Write a folder's Open Knowledge Format index.md: a link to each note directly in it with its description, and one to each subfolder's index.md, replacing the old listing and keeping its frontmatter.

Subfolders without an index.md get one too.

Parameters

Name Type Default Description
folder string "" Folder to index, such as "guides"; omit for the bundle root.

Returns

Dict with:

  • path (str): the index.md written.
  • entries (int): notes and subfolder links listed.
  • frontmatter_preserved (bool): true when the file already had frontmatter, which is kept; false when it had none, though the fields the vault requires are still added.
  • created (list[str]): index.md files written for subfolders that had none, outermost first.
  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

Outcomes and errors

  • TimeoutError: If writes queued before the call have not reached the index within 60 seconds; nothing is changed.
  • IndexUnavailableError: If the index build failed; nothing is changed.
  • DocumentUnreadableError: If the folder's index.md exists but cannot be read; it is left untouched.

okf_generate_index(folder="guides") writes guides/index.md:

# guides

- [Setup](/guides/setup.md) - Install and first run
- [Project Notes/](/guides/Project%20Notes/index.md)

okf_seed_log

OKF: Seed log.md. Changes state, not destructive. Tags: okf, write.

Create a folder's Open Knowledge Format log.md from the git history of the notes under it, one dated section per day, newest first.

It never replaces an existing log.md. It covers the 100 most recent commits. A vault without git history gets an empty log.

Parameters

Name Type Default Description
folder string "" Folder to write log.md in, whose history it covers, such as "guides"; omit for the bundle root and the whole vault's history.

Returns

Dict with path (str), commits (int) and dates (int, the number of distinct days), plus:

  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

Outcomes and errors

  • ToolError: If log.md already exists in the folder; nothing is written.
  • DocumentUnreadableError: If log.md exists but cannot be read; it is left untouched.

okf_seed_log(folder="guides") writes guides/log.md and returns {"path": "guides/log.md", "commits": 3, "dates": 2}:

# Log

## 2026-09-30

- **Add setup guide** (a1b2c3d)
- **Fix typo** (e4f5a6b)

## 2026-09-28

- **Start guides** (0c9d8e7)

okf_verify

OKF: Verify Note. Changes state, not destructive. Tags: okf, okf-enforce, write.

Record that a person reviewed a note, raising its Open Knowledge Format trust tier to human-reviewed; returns the verifier recorded.

The user may be asked to confirm the review. The record means a person looked, not that the content is correct.

Parameters

Name Type Default Description
path string required Path of the note the user reviewed.

Returns

On the modern protocol's first round, an input request asking the client for human confirmation. After confirmation, a dict with:

  • path: the verified note.
  • verifier: the human:<subject> actor recorded.
  • verified_count: the number of verification entries after the append.
  • remote (dict, optional): present only while the vault's git clone cannot reach its remote, with state, reason, since and detail; the change is committed locally only.

Outcomes and errors

  • ToolError: If the mode's confirmation gate is not met, or the note changed since it was read; call okf_verify again to attest the current text. Under elicit the gate needs a client that can ask the user, and the user's confirmation; under trust-auth, an authenticated identity.
  • DocumentNotFoundError: If the note does not exist.

After the user confirms, okf_verify(path="guides/setup.md") returns {"path": "guides/setup.md", "verifier": "human:alice", "verified_count": 1}, and the note's frontmatter gains:

verified:
- at: '2026-10-03T12:00:00Z'
  by: human:alice