Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Markdown Tools: read_markdown & edit_markdown

Two dedicated tools for navigating and editing Markdown files using heading-based addressing. They replace the need to read raw line ranges or construct fragile string replacements against unstructured text.


read_markdown

Navigate a Markdown file by heading. Without heading/headings params, returns a heading map — the document outline with line numbers.

Parameters

ParamTypeDescription
pathstringMarkdown file path (relative to project root)
headingstringSingle section to read (fuzzy matched)
headingsstring[]Multiple sections in one call (mutually exclusive with heading)
start_line / end_lineintRaw line range fallback (1-indexed, inclusive)

Usage

// Step 1: get the heading map
read_markdown("docs/guide.md")
→ heading map with line numbers

// Step 2: read specific sections
read_markdown("docs/guide.md", headings=["## Auth", "## Config"])
→ both sections in one response

The heading map is the starting point for any markdown edit workflow — always read it first so you know which headings exist before targeting one.


edit_markdown

Edit a Markdown document section by heading. Heading matching is fuzzy## Auth matches ## Authentication — so you don’t need to quote headings exactly.

Actions

ActionDescription
replaceReplace section body (heading line is preserved)
insert_beforeInsert content before the heading
insert_afterInsert content after the section (before next heading)
removeDelete the section and its body
editSurgical string replacement within a section (old_stringnew_string)

Parameters

ParamTypeDescription
pathstringMarkdown file path
headingstringTarget section heading (fuzzy matched)
actionstringOne of the actions above
contentstringNew body for replace/insert_* (heading not included)
old_stringstringFor edit: exact text to find
new_stringstringFor edit: replacement text
replace_allboolFor edit: replace all occurrences (default: false)
editsarrayBatch mode — multiple operations applied atomically

Examples

// Replace a section body
edit_markdown("docs/guide.md",
  heading="## Configuration",
  action="replace",
  content="See project.toml for all options.\n")

// Surgical fix inside a section
edit_markdown("docs/guide.md",
  heading="## Auth",
  action="edit",
  old_string="secret_key = \"\"",
  new_string="secret_key = \"<your-key>\"")

// Batch: two edits in one atomic call
edit_markdown("docs/guide.md",
  edits=[
    { heading: "## Usage", action: "replace", content: "..." },
    { heading: "## License", action: "remove" }
  ])

Batch Mode

Pass an edits array instead of heading/action to apply multiple operations atomically. All edits are validated before any are applied — if one heading is missing, nothing changes.


at Parameter for insert_after

When the section’s body contains nested sub-headings, insert_after needs to know whether you want the new content at the end of the section (after all sub-sections) or immediately after the heading line (before any sub-section). Pass at to disambiguate:

at valuePlacement
"end-of-section" (default)After all nested sub-sections — useful for adding a new H3 to an existing H2.
"after-heading-line"Immediately after the heading line itself — useful when a top-level H1 wraps the entire document and “end-of-section” would mean EOF.
// Add a new H3 inside an existing H2 section
edit_markdown("docs/guide.md",
  heading="## Configuration",
  action="insert_after",
  content="\n### Environment Variables\n\n...\n",
  at="end-of-section")

// Add a top-of-page note right under a wrapping H1
edit_markdown("docs/guide.md",
  heading="# Guide",
  action="insert_after",
  content="\n> Note: requires v0.5+.\n",
  at="after-heading-line")

Frontmatter Mutation

Status: experimental — see Experimental Features.

Pass frontmatter: { set, delete } to mutate the YAML frontmatter block in the same atomic call as any body edits. The mutator preserves existing key order — updated keys stay in place; new keys append at the end of the block.

FieldTypeEffect
frontmatter.setobjectKey → value pairs to write. Scalars, strings, booleans, null, or inline arrays only — flat structure, no nested objects.
frontmatter.deletestring[]Keys to remove. Missing keys are silently ignored (idempotent).

At least one of set / delete must be non-empty.

// Close a bug file: flip status, set closed date, drop a legacy field
edit_markdown("docs/issues/2026-05-18-foo.md",
  edits=[
    { heading: "## Fix", action: "replace", content: "Shipped in abc1234.\n" }
  ],
  frontmatter={
    set: { status: "fixed", closed: "2026-05-18" },
    delete: ["legacy_field"]
  })

The frontmatter mutation runs alongside any heading+action or edits[] block in the same call — all changes are validated and applied atomically. If the file has no existing frontmatter block, set operations prepend one; delete is a no-op.

Constraints:

  • Flat YAML only — one key per line, scalar / string / inline-array values. Nested objects raise an error.
  • Keys must be non-empty and contain no whitespace or colons.
  • If the file starts with --- but the closing delimiter is missing, the mutator refuses to guess and returns a “frontmatter is malformed” error.

Why Not edit_file?

edit_file works on raw strings and requires exact whitespace/newline matching. For Markdown, heading-scoped edits are both safer and more resilient:

Scenarioedit_fileedit_markdown
Replace a section bodyError-prone: must match surrounding blank lines exactlyaction=replace — heading preserved automatically
Edit text inside a sectionWorks, but edits anywhere in the fileaction=edit scoped to one section
Remove a sectionMust know exact start/end linesaction=remove — no line numbers needed
Multiple editsMultiple calls, each can conflictedits=[] batch — atomic