MCP server for a minimal, self-hosted, block-based CMS. Allows AI assistants to read pages and blog posts, and create/update blog posts. No delete actions exposed.
Strong foundation with well-structured tool definitions, comprehensive descriptions, and proper JSON schemas. All 9 tools have clear names following verb_noun convention (list_, get_, create_, update_, find_, add_). Descriptions are detailed and actionable (avg ~180 chars), exceeding the 10-1024 char baseline. Tool annotations (readOnlyHint, destructiveHint, idempotentHint) are correctly applied. However, some parameter descriptions lack format constraints (e.g., 'slug' in get_post has no pattern or example), and output schemas are not explicitly documented in the source. Error handling guidance is minimal, tools do not describe recovery paths for common failures.
Turn an image URL into a hosted URL you can safely use in a post. ALWAYS call this before putting an image in a post body or setting a hero image - do not paste raw external URLs into content yourself. If the user has connected Cloudinary, the image is uploaded into their own media library and a permanent Cloudinary URL is returned. If Cloudinary is not connected, the original URL is validated and returned as-is (the response's 'hosted' field tells you which happened). The response includes a ready-to-paste Markdown snippet.
Create a new blog post. The body is written in Markdown (headings, lists, bold, italic, links, code, blockquotes, images) and is converted to the site's format automatically. Lands as a DRAFT unless you pass status. To add images, first call add_image and embed the returned Markdown, or pass its URL as heroImageUrl. Set the category by name - it is created if it does not exist. If a post with the generated slug already exists this returns an error instead of overwriting; pass a different slug or use update_post. Never re-run a successful create as a retry.
Resolve a post the user mentioned by title to its slug, without guessing. Matching is forgiving: casing, spacing, punctuation and small typos are tolerated. If one post matches it returns that post's slug and title; if several match it returns the candidates so you can ask which one; if none match it says so. Use this whenever the user names a post by its title rather than its slug.
Read one page by slug, including its content rendered as Markdown so you understand the existing structure and tone. Read-only. The home page has slug 'home' and serves '/'.
Output schemas not documented in source code. While input schemas are complete and well-typed, return types for all 9 tools are not explicitly defined. LLMs cannot plan downstream tool calls or extract required fields without knowing what each tool returns.
Parameter format constraints missing. 'slug' parameters (get_page, get_post, update_post) lack regex patterns, length limits, or character restrictions. LLMs cannot validate input format and may pass invalid slugs. Description should state: 'URL-safe slug (lowercase letters, numbers, hyphens; 1-100 chars)'.
Error handling lacks recovery guidance. Tools do not describe what to do on failure (e.g., 'If slug not found, try list_posts() to find the correct slug'). Agents receive errors but no actionable next steps.
Inferred effective spec: 2026-07-28+.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | A | 83 | 2026-07-28+ | v2 |
Read one blog post by slug. Returns the full body as Markdown plus title, excerpt, category, tags, hero image and SEO fields. ALWAYS call this before update_post so you can edit the real current body instead of overwriting it blindly.
List the blog's categories. Use this to pick a valid category name before assigning one to a post.
List the site's pages with slug, title, status and public path. Pages are READ-ONLY through this connector - you can read them for context but cannot create, edit, or delete them. To change page content the user must use the visual editor at /admin.
List blog posts (newest first) with slug, title, status, category, tags and excerpt. Metadata only - call get_post for the full body. Optionally filter by status.
Update an existing blog post by slug. Only the fields you provide change. IMPORTANT: content, when provided, REPLACES the entire body, so call get_post first, edit the returned Markdown, and send back the full edited body - do not send only the new part. Body is Markdown and is converted automatically. Use add_image for any images. This connector cannot delete posts.
Pagination not implemented for list tools. list_pages, list_posts, and list_categories return all results without limit, offset, or cursor parameters. Large result sets will exhaust context windows and degrade LLM reasoning.
Confirmation step missing for irreversible operations. create_post and update_post modify content but lack dry-run or confirmation patterns. Agents can accidentally publish or overwrite posts without user approval.