This is the slice of the YouTeacher MCP server that lets an agent (Claude Code, Claude Desktop) write and manage the blog directly. Every tool here is a thin pass-through over the content service's HTTP API — same inputs, same JSON responses — so the agent never has to rebuild that API surface inside its own prompt. There is no client-side gating: authorization is enforced on the server, and each call carries whatever the calling auth client holds (a key-derived admin session). The write endpoints require admin on the server side no matter which tool issued the call.
Post CRUD
The post tools wrap /api/content/v1/posts/*.
- Read —
list_posts(filters by status, category id or slug, author, pluslimit/page),get_post_by_id(by cuid),get_post_by_slug. Admins see drafts as well as published posts. - Write —
create_post,update_post(partial — only the fields you pass change),delete_post(idempotent; the service cleans up attachments).
A post's body is stored as-is in one of three formats, chosen by contentFormat: blocknote (the default editor format), html, or markdown. For a "hand me markdown and figure it out" entry point, the markdown-import tool is preferred over create_post.
Scheduled publishing and unpublishing
publish_post publishes now when publishedAt is omitted. Pass a future ISO-8601 publishedAt and the post is scheduled: it stays hidden from the public site and from list_posts(status=published) until that date, while the author still sees it by listing with no status filter. This is what lets an agent write a batch now and drip-publish it over future dates. unpublish_post moves a published post back to draft, at which point its public URL stops resolving until it is republished.
URL aliases
Beyond the canonical /content/{slug} URL, a post can carry one human-friendly alias. set_post_alias sets or replaces it (a clash with an existing alias returns HTTP 409); delete_post_alias removes it, and the canonical URL keeps working either way.
Categories
Categories wrap /api/content/v1/categories. They are not blog content themselves, but create_post / import_markdown_post take a categorySlug or categoryId, so an agent usually looks one up or creates one first. list_categories returns a flat array; get_category_tree returns parents with their children inlined for picking by hierarchy. create_category takes an optional parentId (omit for top-level); update_category edits name and/or description; delete_category clears the categoryId on any posts that were in it rather than deleting those posts.
Markdown → BlockNote import
import_markdown_post is the agent-friendly authoring path. It sends a full markdown body to the server (as multipart form data), and the server converts markdown into BlockNote, infers the title from the first # H1 when no title is given, and links a category by slug. Three things ride along with the markdown:
- Inline images — referenced as
, with each file's absolute path passed inmediaPaths; the basenames must match themedia:references. Externalhttp(s)image URLs in the markdown are kept as-is. A single hero image goes throughfeaturedImagePath. Only image types are accepted (PNG, JPEG, GIF, WEBP, SVG). - Custom blocks via directive syntax — a leaf block is
::name{key="value"}and a container block is:::name… body …:::. The directive name maps 1:1 to the BlockNote block type. A container's body becomesprops.contentas a raw string (content-heavy blocks such as faq / cta / callout store JSON there); a leaf's attributes become the block's props. For example,::youtube{url="…"}is an embed and a:::faqblock carries a JSON array of question/answer pairs.
The live block registry
get_supported_blocks returns the editor's live slash-command registry (/api/seo/embed-registry): every custom block the editor can render — youtube, instagram, tiktok, pdf, faq, cta, callout, and any future additions — the directive syntax for each, and a canonical example article using every block, meant to be read as a few-shot demo. This registry is the single source of truth: a block added to the web app's embed registry shows up here on the next deploy, with no change to the MCP server. So the set of importable custom blocks stays in sync with the editor without touching this code.
Per-post export
Two export tools render an existing post back out: export_post_markdown (returns the markdown body inline and writes it to disk) and export_post_pdf (writes the PDF and returns a base64 copy so a caller without filesystem access can still consume the bytes). Both write under YOUTEACHER_MCP_DOWNLOAD_DIR, or the OS temp directory when that is unset, and both require an admin session.
Inside this node
- youteacher_mcp — the MCP server this authoring surface belongs to
- youteacher_content — the content service whose HTTP API these tools wrap