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=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 withMARKDOWN_VAULT_MCP_REPORT_UNRESOLVED_LINKS=true. The target, as written, of each link this write added thatget_broken_linksreports; 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_base64is missing/invalid for attachments, or the content exceedsMARKDOWN_VAULT_MCP_MAX_ATTACHMENT_SIZE_MB. MCPError: Ifif_matchis provided and the file has been modified, orif_matchis supplied for a file that does not yet exist (ConcurrentModificationError). Also when the server runs withMARKDOWN_VAULT_MCP_WRITE_PROTECT_EXISTING=true(default) and path already exists while noif_matchis supplied (DocumentExistsError); use 'edit' or 'append' instead, or read the file first and pass its etag asif_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 withMARKDOWN_VAULT_MCP_REPORT_UNRESOLVED_LINKS=true. The target, as written, of each link this edit added thatget_broken_linksreports; 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_textis 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: Ifif_matchis 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_missingcreated 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 withMARKDOWN_VAULT_MCP_REPORT_UNRESOLVED_LINKS=true. The target, as written, of each link this append added thatget_broken_linksreports; 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_missingis false. MCPError: Ifif_matchis 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: Ifif_matchis 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_pathdoes not exist. - DocumentExistsError: If
new_pathalready exists. - ValueError: If the path fails traversal validation.
MCPError: Ifif_matchis provided and the file has been modified (ConcurrentModificationError).- TimeoutError: With
update_linkson a note, if writes queued before the call have not reached the index within 60 seconds; nothing is changed. - IndexUnavailableError: With
update_linkson 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 downloadedcontent_type(str or null): Content-Type from the responsefinal_url(str): the URL the bytes actually came from: equal tourlwhen 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 noif_matchis supplied. The save routes through the same guardedwrite/write_attachmentpath as the write tools, so read the existing note first and pass its etag asif_matchto 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_boundaryturns it into a"retry later"answer.httpx.TransportError: On a timeout or a failed connection; it propagates totool_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_links¶
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: thehuman:<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_verifyagain to attest the current text. Underelicitthe gate needs a client that can ask the user, and the user's confirmation; undertrust-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