Add frontmatter tools, append, delete + pyyaml dep

This commit is contained in:
helm 2026-07-08 20:58:01 -04:00
parent 39fae1501b
commit b27728d75a
2 changed files with 146 additions and 1 deletions

View file

@ -1 +1,2 @@
fastmcp>=2.0.0 fastmcp>=2.0.0
pyyaml>=6.0

144
server.py
View file

@ -15,6 +15,7 @@ from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
import yaml
from fastmcp import FastMCP from fastmcp import FastMCP
# ── Config ──────────────────────────────────────────────────────────────── # ── Config ────────────────────────────────────────────────────────────────
@ -599,6 +600,149 @@ def move_document(vault_name: str, src_path: str, dest_path: str) -> dict[str, A
} }
@mcp.tool()
def read_frontmatter(vault_name: str, doc_path: str, key: str | None = None) -> dict[str, Any]:
"""Read YAML frontmatter from a markdown document.
Args:
vault_name: Name of the vault.
doc_path: Relative path to the document.
key: Optional specific key to read. If None, returns all frontmatter.
Returns the frontmatter dict or specific key value.
"""
target = _resolve_doc(vault_name, doc_path)
if not target.exists():
return {"error": f"Document not found: {doc_path}", "vault": vault_name}
content = target.read_text(encoding="utf-8", errors="replace")
fm = _parse_frontmatter(content)
if fm is None:
return {"vault": vault_name, "path": doc_path, "frontmatter": {}, "note": "No frontmatter found"}
if key:
return {"vault": vault_name, "path": doc_path, "key": key, "value": fm.get(key)}
return {"vault": vault_name, "path": doc_path, "frontmatter": fm}
@mcp.tool()
def write_frontmatter(vault_name: str, doc_path: str, key: str, value: Any = None) -> dict[str, Any]:
"""Set a YAML frontmatter key on a markdown document. Creates frontmatter if absent.
Args:
vault_name: Name of the vault.
doc_path: Relative path to the document.
key: Frontmatter key to set.
value: Value to set (string, number, boolean, list, or dict). Pass null/None to delete.
Returns the updated frontmatter state.
"""
target = _resolve_doc(vault_name, doc_path)
if not target.exists():
return {"error": f"Document not found: {doc_path}", "vault": vault_name}
content = target.read_text(encoding="utf-8", errors="replace")
fm = _parse_frontmatter(content)
if fm is None:
# No frontmatter — create one
if value is None:
return {"vault": vault_name, "path": doc_path, "action": "noop", "note": "No frontmatter to delete from"}
new_fm = {key: value}
new_content = "---\n" + yaml.dump(new_fm, default_flow_style=False, allow_unicode=True).strip() + "\n---\n\n" + content
target.write_text(new_content, encoding="utf-8")
return {"vault": vault_name, "path": doc_path, "key": key, "value": value, "action": "created"}
# Existing frontmatter
if value is None:
fm.pop(key, None)
action = "deleted"
else:
fm[key] = value
action = "set"
new_fm_block = "---\n" + yaml.dump(fm, default_flow_style=False, allow_unicode=True).strip() + "\n---"
body = _body_after_frontmatter(content)
target.write_text(new_fm_block + "\n\n" + body, encoding="utf-8")
return {"vault": vault_name, "path": doc_path, "key": key, "value": value, "action": action}
@mcp.tool()
def append_to_document(vault_name: str, doc_path: str, content: str) -> dict[str, Any]:
"""Append content to the end of a markdown document. Creates the file if it doesn't exist.
Args:
vault_name: Name of the vault.
doc_path: Relative path to the document.
content: Markdown content to append.
Returns the updated document info.
"""
if not doc_path.lower().endswith(".md"):
doc_path = doc_path.rstrip("/\\") + ".md"
target = _resolve_doc(vault_name, doc_path)
if not target.exists():
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content + "\n", encoding="utf-8")
action = "created"
else:
existing = target.read_text(encoding="utf-8", errors="replace")
separator = "\n" if existing.endswith("\n") else "\n\n"
target.write_text(existing + separator + content + "\n", encoding="utf-8")
action = "appended"
return {
"vault": vault_name,
"path": doc_path.lstrip("/\\").replace("\\", "/"),
"action": action,
"size_bytes": target.stat().st_size,
}
@mcp.tool()
def delete_document(vault_name: str, doc_path: str) -> dict[str, Any]:
"""Delete a document from a vault.
Args:
vault_name: Name of the vault.
doc_path: Relative path to the document.
Returns deletion confirmation.
"""
target = _resolve_doc(vault_name, doc_path)
if not target.exists():
return {"error": f"Document not found: {doc_path}", "vault": vault_name}
target.unlink()
return {"vault": vault_name, "path": doc_path.lstrip("/\\").replace("\\", "/"), "action": "deleted"}
def _parse_frontmatter(content: str) -> dict[str, Any] | None:
"""Parse YAML frontmatter from markdown content. Returns None if not found."""
if not content.startswith("---"):
return None
parts = content.split("---", 2)
if len(parts) < 3:
return None
try:
return yaml.safe_load(parts[1]) or {}
except yaml.YAMLError:
return None
def _body_after_frontmatter(content: str) -> str:
"""Return the body content after YAML frontmatter."""
if not content.startswith("---"):
return content
parts = content.split("---", 2)
if len(parts) < 3:
return content
return parts[2].lstrip("\n")
# ── Entrypoint ──────────────────────────────────────────────────────────── # ── Entrypoint ────────────────────────────────────────────────────────────
def main(): def main():