Tool reference

Blog tools

All 12 blog MCP tools with input shape, response shape, and example calls.

Twelve tools cover every blog operation an agent might want — discovery, content writes, lifecycle management, and analytics (stubbed for now).

All endpoints follow the pattern POST https://mcp.vlozi.app/tools/blog.<name> with Authorization: Bearer ls_xxx. All responses use this envelope:

{ "data": <payload>, "error": null }   // success
{ "data": null,      "error": "..." }  // failure (with HTTP 4xx/5xx)

Discovery

blog.list_posts

List posts in your workspace.

Scope: blog:posts.read

Input:

Field Type Notes
status "draft" | "published" | "scheduled" Filter by state
page number 1-based, default 1
limit number Default 20, max 100
sort "createdAt" | "publishedAt" | "title" Default createdAt
order "asc" | "desc" Default desc

Response: { posts: Post[], pagination: { page, limit, total, totalPages, hasMore } }

Each Post includes id, title, slug, status, excerpt, categoryId, featuredImageUrl, seoTitle, seoDescription, scheduledFor, publishedAt, createdAt, updatedAt, tags[].

curl -X POST https://mcp.vlozi.app/tools/blog.list_posts \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -H "content-type: application/json" \
  -d '{"status": "published", "limit": 10, "sort": "publishedAt"}'

blog.get_post

Get one post by id OR slug — pass exactly one. Returns the full TipTap content body plus hydrated tags and category.

Scope: blog:posts.read

Input: { id?: string, slug?: string }

Response: { post: FullPost }FullPost includes content (TipTap doc), tags[], and category.

# By id
curl -X POST https://mcp.vlozi.app/tools/blog.get_post \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -d '{"id": "post_xxx"}'
 
# By slug
curl -X POST https://mcp.vlozi.app/tools/blog.get_post \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -d '{"slug": "hello-world"}'

blog.search_posts

Substring search across title, slug, and excerpt (case-insensitive).

Scope: blog:posts.read

Input: { query: string, limit?: number } (limit default 20, max 50)

Response: { posts: PostSummary[], query: string }

TIP

For semantic search across post bodies, use brain.query against ingested content instead.

curl -X POST https://mcp.vlozi.app/tools/blog.search_posts \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -d '{"query": "ai", "limit": 5}'

blog.list_categories

List all categories with how many posts use each.

Scope: blog:posts.read

Input: { limit?: number } (default 100, max 500)

Response: { categories: { id, name, slug, postCount }[] }

Call this before passing categoryId to create_draft or update_post so your agent can pick a real one.


blog.list_tags

List all tags with how many posts use each.

Scope: blog:posts.read

Input: { limit?: number } (default 100, max 500)

Response: { tags: { id, name, slug, postCount }[] }

NOTE

When creating or updating posts, pass tag names (not IDs) — the service upserts new tags automatically. Use list_tags to see what already exists if you want to reuse them.

Content writes

blog.create_draft

Create a new draft.

Scope: blog:posts.create

Input:

Field Type Notes
title string (required) Max 255 chars
content string | object Plain text (auto-wrapped as paragraph) or full TipTap doc
slug string Auto-generated from title if omitted; collision-safe
excerpt string Max 1000 chars
categoryId string Must exist in your workspace
tags string[] Tag names (not IDs); new tags auto-created
seoTitle string Defaults to title
seoDescription string Max 500 chars
featuredImageUrl string

Response: { post: FullPost } with status: "draft".

# Plain text
curl -X POST https://mcp.vlozi.app/tools/blog.create_draft \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "title": "My first agent-written post",
    "content": "Hello, world. I am an AI.",
    "tags": ["ai-generated", "intro"],
    "excerpt": "Drafted by an agent."
  }'
 
# Rich content (TipTap doc)
curl -X POST https://mcp.vlozi.app/tools/blog.create_draft \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "title": "Rich content",
    "content": {
      "type": "doc",
      "content": [
        { "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "Intro" }] },
        { "type": "paragraph", "content": [{ "type": "text", "text": "Body paragraph." }] }
      ]
    }
  }'

blog.update_post

Update any subset of fields. Omit a field to leave it unchanged.

Scope: blog:posts.update

Input: Same shape as create_draft plus required id. Special semantics:

Behavior How
Replace tag set Pass tags: ["a", "b"] — REPLACES existing tags
Clear all tags Pass tags: []
Clear category Pass categoryId: null
Keep existing tags Omit tags from request

WARNING

tags is a replace, not a merge. To preserve existing tags while adding new ones, call get_post first, then send the merged list.

Response: { post: FullPost }

curl -X POST https://mcp.vlozi.app/tools/blog.update_post \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "id": "post_xxx",
    "title": "Updated title",
    "excerpt": "Refreshed by an agent."
  }'

blog.delete_post

Permanently delete a post. Tags are cascade-deleted from the junction table; the category itself is untouched.

Scope: blog:posts.delete

Input: { id: string }

Response: { id: string, deleted: true }

CAUTION

This is irreversible. Agents should confirm with the user before calling.

Lifecycle

blog.publish_post

Publish a draft immediately, or schedule it for a future ISO 8601 timestamp.

Scope: blog:posts.publish

Input: { id: string, scheduledFor?: string }

scheduledFor value Result
Omitted Publishes immediately, sets publishedAt: now, status → "published"
Future ISO 8601 Schedules, sets scheduledFor, status → "scheduled"
Past or invalid 400 invalid_input — rejected

Response: { post: { id, status, publishedAt, scheduledFor } }

# Publish now
curl -X POST https://mcp.vlozi.app/tools/blog.publish_post \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -d '{"id": "post_xxx"}'
 
# Schedule for tomorrow 9am UTC
curl -X POST https://mcp.vlozi.app/tools/blog.publish_post \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -d '{"id": "post_xxx", "scheduledFor": "2026-05-15T09:00:00Z"}'

blog.unpublish_post

Move a published or scheduled post back to draft. Public URLs stop serving it.

Scope: blog:posts.publish

Input: { id: string }

Response: { post: { id, status } }


blog.unschedule_post

Clear a post's scheduled publish time, returning it to draft. Does not affect already-published posts — calling this on a live post returns 409 to prevent accidental takedowns.

Scope: blog:posts.publish

Input: { id: string }

Response on scheduled post: { post: { id, status: "draft" } } Response on published post: HTTP 409, { error: "post is not scheduled (current status: published)" }

Analytics

blog.get_analytics

Get analytics for a post.

Scope: blog:posts.read

Input: { id: string, from?: string, to?: string }

Response:

{
  "post": { "id": "post_xxx", "title": "...", "status": "published", "publishedAt": "..." },
  "analytics": { "views": 0, "reads": 0, "avgScrollDepth": null },
  "collected": false,
  "note": "Analytics ingestion is not yet enabled — values are placeholders."
}

NOTE

The real analytics pipeline isn't yet wired — collected: false until ingestion ships.

For a complete end-to-end agent flow, see the content pipeline recipe.

MCP · Tool referenceEdit on GitHub