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 typing import Any
import yaml
from fastmcp import FastMCP
# ── 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 ────────────────────────────────────────────────────────────
def main():