Copied to clipboard!

Vibe CMS includes a powerful Model Context Protocol (MCP) server that enables AI agents like Claude Code to manage content, collections, files, and locales programmatically. This chapter covers the MCP architecture, available tools, and common workflows for AI-assisted content management.

Prerequisites

Before proceeding, ensure you have:


MCP Server Architecture

Vibe CMS implements the Model Context Protocol using FastMCP, a production-ready framework for building MCP servers with FastAPI integration.

Core Components

1. FastMCP Application (/backend/app/services/mcp/app.py)

The MCP server is defined using FastMCP and includes:

from fastmcp import FastMCP

mcp = FastMCP(
    "Vibe CMS Server",
    instructions="""CMS with project-scoped operations.
    Collections contain content items with translations per locale.
    All content item edits are performed on a draft version.
    After content item edits, you must ask the user to approve the
    changes going to the web app and publishing the draft version.
    Files stored in folders with metadata tracking.""",
)

The server registers 6 categories of tools:

  • CollectionTools - Manage collections and schemas
  • FieldTools - Add, update, reorder, and delete fields
  • ContentItemTools - Create and manage content with translations
  • FileTools - Upload, manage, and retrieve files
  • FolderTools - Organize files in folder structures
  • LocaleTools - Configure supported languages

2. Authentication Middleware (/backend/app/services/mcp/middleware.py)

The MCPAuthMiddleware intercepts all requests to /mcp/* endpoints and:

  1. Extracts API key from headers (X-API-Key or Authorization: Bearer)
  2. Validates the key via authenticate_with_api_key()
  3. Creates an authenticated SupabaseService instance
  4. Stores project context in request.state
class MCPAuthMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        if not request.url.path.startswith("/mcp"):
            return await call_next(request)
        
        api_key = extract_api_key_from_headers(request.headers)
        if not api_key:
            return JSONResponse(status_code=401, content={"error": "Authentication required"})
        
        service, key_info = await authenticate_with_api_key(api_key)
        request.state.supabase = service
        request.state.project_id = key_info.get("project_id")
        
        return await call_next(request)

3. Database-Layer Security

Vibe CMS implements a zero-trust architecture where every database operation independently validates the API key:

CREATE OR REPLACE FUNCTION public.get_collections_for_project(
    p_project_id uuid,
    p_api_key text DEFAULT NULL
)
RETURNS TABLE (...) AS $$
BEGIN
    -- Validate API key and set session context
    PERFORM ensure_authenticated(p_api_key);
    
    -- Query with RLS enforcement
    RETURN QUERY SELECT * FROM collections WHERE project_id = p_project_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

Key security features:

  • API key validation: ensure_authenticated(p_api_key) uses bcrypt (timing-safe)
  • Session variables: Sets app.current_project_id, app.current_account_id, app.auth_type
  • RLS policies: Enforce project-scoped access on all tables
  • No session pooling: Each RPC call is independently authenticated

Request Flow

Claude Code
    ↓
    ↓ HTTP POST /mcp/ (with X-API-Key header)
    ↓
MCPAuthMiddleware
    ↓ Extract & validate API key
    ↓ Create authenticated SupabaseService
    ↓
FastMCP Tool Handler
    ↓ Get service from request.state.supabase
    ↓
SupabaseService Method
    ↓ Add p_api_key to RPC params
    ↓
Database RPC Function
    ↓ Call ensure_authenticated(p_api_key)
    ↓ Execute query with RLS enforcement
    ↓
Response to Claude Code

Available MCP Tools

Vibe CMS provides 18+ tools organized into 6 functional categories.

Collections Tools

collections(slug?)

Query collections with optional slug filter.

Parameters:

  • slug (optional): Collection slug (e.g., 'blog-posts'). If provided, returns single collection with fields. If omitted, lists all collections.

Response (list all):

{
  "collections": [
    {
      "name": "Blog Posts",
      "slug": "blog-posts",
      "description": "Main blog content",
      "is_singleton": false,
      "fields": [
        {
          "field_name": "title",
          "field_type": "text",
          "interface_type": "input",
          "is_required": true,
          "sort_order": 0
        }
      ]
    }
  ],
  "count": 1
}

Response (single collection):

{
  "collection": {
    "name": "Blog Posts",
    "slug": "blog-posts",
    "description": "Main blog content",
    "is_singleton": false,
    "fields": [...]
  },
  "message": "Collection 'blog-posts' retrieved"
}

manage_collection(action, slug, ...)

Create, update, or delete collection.

Parameters:

  • action (required): "create", "update", or "delete"
  • slug (required): Collection slug
  • name (required for create/update): Display name
  • description (optional): Collection description
  • is_singleton (optional): Single-item collection flag
  • confirm_delete (required for delete): Must be true to confirm

Example (create):

{
  "action": "create",
  "slug": "faq",
  "name": "FAQ Items",
  "description": "Frequently asked questions",
  "is_singleton": false
}

Field Tools

add_collection_field(collection_slug, name, field_type, interface_type, ...)

Add a new field to a collection.

Parameters:

  • collection_slug (required): Collection slug (e.g., 'blog-posts')
  • name (required): Field name (lowercase, letters/numbers/underscores, must start with letter)
  • field_type (required): Data type - "text", "markdown", "number", "boolean", or "file"
  • interface_type (required): UI interface - "input", "textarea", "markdown", "single_file", or "multiple_files"
  • is_required (optional, default false): Whether field is required
  • sort_order (optional, default 0): Field ordering

Field Type Compatibility:

  • text: "input", "textarea"
  • markdown: "markdown"
  • number: "input"
  • boolean: "input"
  • file: "single_file", "multiple_files"

Reserved Field Names: Cannot use id, created_at, updated_at, data, select, from, where, and, or, not, null, true, false, table, column

update_collection_field(collection_slug, field_name, ...)

Update field interface, required status, or sort order.

Parameters:

  • collection_slug (required): Collection slug
  • field_name (required): Name of field to update
  • interface_type (optional): New UI interface (must be compatible with field type)
  • is_required (optional): New required status
  • sort_order (optional): New sort order

Note: Cannot change field name or field type after creation.

reorder_collection_fields(collection_slug, field_names)

Reorder all fields in a collection.

Parameters:

  • collection_slug (required): Collection slug
  • field_names (required): List of ALL field names in desired order (first to last)

Example:

{
  "collection_slug": "blog-posts",
  "field_names": ["title", "slug", "content", "author", "published_date"]
}

delete_collection_field(collection_slug, field_name, confirm)

Delete field from collection permanently with all data.

Parameters:

  • collection_slug (required): Collection slug
  • field_name (required): Name of field to delete
  • confirm (required): Must be true to confirm deletion

Warning: This permanently deletes the field and all its data across all content items.

Content Tools

content(collection_slug, content_item_id?, locale?)

Get content items - list all in collection or get single item with translations.

Parameters:

  • collection_slug (required): Collection slug (e.g., 'blog-posts')
  • content_item_id (optional): Content item UUID. If provided, returns single item with translations.
  • locale (optional): Locale code filter (e.g., 'en-US'). Only used with content_item_id.

Response (list all):

{
  "items": [
    {
      "id": "uuid-here",
      "collection_slug": "blog-posts",
      "status": "published",
      "description": "My first post"
    }
  ],
  "count": 1,
  "collection_slug": "blog-posts"
}

Response (single item):

{
  "id": "uuid-here",
  "collection_slug": "blog-posts",
  "status": "published",
  "description": "My first post",
  "translations": [
    {
      "locale": "en-US",
      "data": {
        "title": "Hello World",
        "content": "Welcome to my blog..."
      }
    }
  ]
}

create_content(collection_slug, description?, status?)

Create new content item metadata without translation data.

Parameters:

  • collection_slug (required): Collection slug
  • description (optional): Content item description
  • status (optional, default "draft"): "draft", "published", or "archived"

Response:

{
  "id": "uuid-here",
  "collection_slug": "blog-posts",
  "status": "draft",
  "message": "Content item created successfully. Use update_content_translation to add content data for each locale."
}

update_content_translation(content_item_id, locale, data, ...)

Create or update content item translation with schema validation.

Parameters:

  • content_item_id (required): Content item UUID
  • locale (required): Locale code (e.g., 'en-US', 'de-DE')
  • data (required): Content data matching collection field schema
  • status (optional): Update status ("draft", "published", "archived")
  • description (optional): Update description

Example:

{
  "content_item_id": "uuid-here",
  "locale": "en-US",
  "data": {
    "title": "My First Blog Post",
    "slug": "first-post",
    "content": "# Welcome\n\nThis is my first post...",
    "author": "John Doe",
    "published_date": "2024-01-15"
  },
  "status": "published"
}

Validation:

  • Checks all fields match collection schema
  • Validates required fields on create
  • Prevents setting required fields to null on update
  • Returns clear error messages for validation failures

delete_content(content_item_id, locale?, confirm_delete)

Delete content item or specific translation.

Parameters:

  • content_item_id (required): Content item UUID
  • locale (optional): Locale code. If provided, deletes only that translation. If omitted, deletes entire item.
  • confirm_delete (required): Must be true to confirm

Note: If you delete the last translation, the content item is automatically deleted.

File Tools

files(file_id?, folder_path?, mime_type?)

Get file metadata by ID or list files with optional filters.

Parameters:

  • file_id (optional): File ID to get metadata for. If provided, returns single file metadata with public URL.
  • folder_path (optional): Folder path filter for listing (e.g., '/images', '/documents/2024')
  • mime_type (optional): MIME type filter for listing (e.g., 'image/png', 'image/')

Response (single file):

{
  "id": "file-uuid",
  "filename": "hero.jpg",
  "mime_type": "image/jpeg",
  "file_size": 245678,
  "width": 1920,
  "height": 1080,
  "alt_text": "Hero image",
  "title": "Homepage Hero",
  "public_url": "https://cdn.example.com/files/hero.jpg"
}

manage_file(action, file_id, ...)

Update file metadata or delete file permanently.

Parameters:

  • action (required): "update" or "delete"
  • file_id (required): File ID to manage
  • title (optional, update only): File title
  • alt_text (optional, update only): SEO alt text
  • caption (optional, update only): File caption
  • description (optional, update only): File description
  • focus_keyword (optional, update only): SEO focus keyword
  • folder_path (optional, update only): New folder path
  • confirm_delete (required for delete): Must be true to confirm

get_file_preview(file_id)

Generate 512x512 preview image for image files only.

Parameters:

  • file_id (required): File ID

Returns: Base64-encoded preview image or error if file is not an image.

upload_file_from_url(url, ...)

Download file from URL and upload to CMS.

Parameters:

  • url (required): URL to download file from
  • filename (optional): Override filename (auto-detected if not provided)
  • mime_type (optional): Override MIME type (auto-detected if not provided)
  • folder_path (optional): Folder path (e.g., '/images', '/documents/2024')
  • title (optional): File title
  • alt_text (optional): SEO alt text for images
  • caption (optional): File caption
  • description (optional): File description
  • focus_keyword (optional): SEO focus keyword

Example:

{
  "url": "https://images.unsplash.com/photo-123",
  "filename": "hero-image.jpg",
  "folder_path": "/images/heroes",
  "alt_text": "Beautiful mountain landscape",
  "title": "Hero Image - Mountains"
}

request_upload_token(filename, mime_type, file_size, ...)

Request temporary token for direct HTTP PUT file upload.

Parameters:

  • filename (required): Filename to upload (e.g., 'document.pdf', 'image.jpg')
  • mime_type (required): File MIME type (e.g., 'application/pdf', 'image/jpeg')
  • file_size (required): File size in bytes (max 52428800 = 50MB)
  • folder_path (optional): Folder path
  • title (optional): File title
  • alt_text (optional): SEO alt text

Response:

{
  "upload_url": "https://storage.example.com/upload?token=...",
  "file_id": "uuid-here",
  "expires_at": "2024-01-15T12:30:00Z"
}

Usage: Use the upload_url to perform a direct HTTP PUT request with the file contents.

Folder Tools

folders(parent_path?)

List all folders with optional parent path filter.

Parameters:

  • parent_path (optional): Parent path to filter folders (e.g., '/images', '/documents/2024')

Response:

{
  "folders": [
    {
      "name": "heroes",
      "path": "/images/heroes",
      "parent_path": "/images"
    }
  ],
  "count": 1,
  "filter_applied": "/images"
}

manage_folder(action, path, ...)

Create, update (rename), or delete folder.

Parameters:

  • action (required): "create", "update", or "delete"
  • path (required): Folder path (e.g., '/images', '/teams')
  • new_path (optional, required for update): New path for rename
  • confirm_delete (required for delete): Must be true to confirm

Example (create):

{
  "action": "create",
  "path": "/images/heroes"
}

Locale Tools

locales()

List all locales for project.

Response:

{
  "locales": [
    {
      "locale_code": "en-US",
      "display_name": "English (US)",
      "is_default": true,
      "is_active": true
    },
    {
      "locale_code": "de-DE",
      "display_name": "German",
      "is_default": false,
      "is_active": true
    }
  ],
  "default_locale": {
    "locale_code": "en-US",
    "display_name": "English (US)",
    "is_default": true,
    "is_active": true
  },
  "count": 2,
  "active_count": 2
}

manage_locale(action, locale_code, ...)

Create, update, or delete locale.

Parameters:

  • action (required): "create", "update", or "delete"
  • locale_code (required): BCP 47 locale code (e.g., 'en-US', 'fr-FR', 'de-DE')
  • display_name (required for create/update): Human-readable locale name
  • is_default (optional): Whether this should be the default locale
  • is_active (optional): Whether this locale is active
  • confirm_delete (required for delete): Must be true to confirm

Example (create):

{
  "action": "create",
  "locale_code": "es-ES",
  "display_name": "Spanish (Spain)",
  "is_active": true
}

Authentication and Security

API Key Format

Vibe CMS uses project-scoped API keys with the format:

sk_[random_string]

Creating an API Key

Via Frontend UI:

  1. Login to Vibe CMS
  2. Navigate to Project Settings → API Keys
  3. Click "Create New API Key"
  4. Provide name and optional expiration
  5. Copy the key (shown only once!)

Via Database Seed (development only):

npm run db:seed
cat /tmp/vibe_cms_test_api_key_project.txt

Using API Keys with MCP

API keys can be provided in two ways:

Option 1: X-API-Key Header

{
  "headers": {
    "X-API-Key": "sk_your_api_key_here"
  }
}

Option 2: Authorization Bearer Header

{
  "headers": {
    "Authorization": "Bearer sk_your_api_key_here"
  }
}

Security Model

Vibe CMS implements a zero-trust security architecture:

  1. No Session State: Each request is independently authenticated
  2. Database-Layer Validation: Every RPC call validates the API key via ensure_authenticated(p_api_key)
  3. Bcrypt Hashing: API keys are hashed using bcrypt (timing-safe comparison)
  4. Session Variables: Authentication sets PostgreSQL session variables:
    • app.auth_type = 'api_key'
    • app.current_project_id = <project_id>
    • app.current_account_id = <account_id>
  5. RLS Enforcement: Row-Level Security policies use session variables for access control
  6. Project Scoping: API keys only access resources within their project
  7. Instant Revocation: Delete API key to immediately terminate all access

Best Practices

  • Never commit API keys to version control
  • Use environment variables for API key storage
  • Rotate keys regularly for production environments
  • Set expiration dates for temporary access
  • Use descriptive names to identify key purpose
  • Monitor API key usage via audit logs

Using MCP Tools in Claude Code

Tool Invocation Syntax

Claude Code automatically detects available MCP tools and can invoke them based on natural language requests.

Example Conversations:

User: "List all collections in my CMS"

Claude Code: Invokes collections() without parameters


User: "Show me the schema for the blog-posts collection"

Claude Code: Invokes collections(slug="blog-posts")


User: "Create a new collection called 'Team Members' with slug 'team'"

Claude Code: Invokes manage_collection(action="create", slug="team", name="Team Members")

Response Handling

All MCP tools return JSON responses with consistent structure:

Success Response:

{
  "success": true,
  "data": { ... },
  "message": "Operation completed successfully"
}

Error Response:

{
  "error": "Error message",
  "suggestion": "How to fix this error"
}

Claude Code automatically:

  • Parses JSON responses
  • Displays results in human-readable format
  • Uses suggestions to retry failed operations
  • Chains multiple tool calls for complex workflows

Error Handling

Common error scenarios and how to handle them:

Invalid Field Name:

{
  "error": "Field name must start with lowercase letter and contain only lowercase letters, numbers, and underscores",
  "suggestion": "Use lowercase letters, numbers, and underscores only. Must start with a letter."
}

Missing Required Parameters:

{
  "error": "name is required for create action",
  "suggestion": "Provide a name for the collection"
}

Resource Not Found:

{
  "error": "Collection 'blog-posts' not found",
  "suggestion": "Use collections() to see available collections"
}

Permission Denied:

{
  "error": "Insufficient permissions to delete this field",
  "suggestion": "Ensure your API key has write access to this project"
}

Common Workflows

1. Creating a New Collection with Fields

Goal: Set up a "Team Members" collection with name, role, bio, and photo fields.

Prompt to Claude Code:

"Create a new collection called 'Team Members' with slug 'team'. Add fields: name (text input, required), role (text input, required), bio (markdown), and photo (single file)."

Claude Code executes:

# Step 1: Create collection
manage_collection(
    action="create",
    slug="team",
    name="Team Members",
    description="Company team member profiles"
)

# Step 2: Add name field
add_collection_field(
    collection_slug="team",
    name="name",
    field_type="text",
    interface_type="input",
    is_required=True,
    sort_order=0
)

# Step 3: Add role field
add_collection_field(
    collection_slug="team",
    name="role",
    field_type="text",
    interface_type="input",
    is_required=True,
    sort_order=1
)

# Step 4: Add bio field
add_collection_field(
    collection_slug="team",
    name="bio",
    field_type="markdown",
    interface_type="markdown",
    is_required=False,
    sort_order=2
)

# Step 5: Add photo field
add_collection_field(
    collection_slug="team",
    name="photo",
    field_type="file",
    interface_type="single_file",
    is_required=False,
    sort_order=3
)

2. Managing Files and Media

Goal: Upload a hero image from Unsplash and organize it in a folder.

Prompt to Claude Code:

"Create a folder at /images/heroes, then download this Unsplash image and upload it to that folder with alt text 'Mountain landscape at sunset'"

Claude Code executes:

# Step 1: Create folder
manage_folder(
    action="create",
    path="/images/heroes"
)

# Step 2: Upload image from URL
upload_file_from_url(
    url="https://images.unsplash.com/photo-1234567890",
    filename="hero-mountains.jpg",
    folder_path="/images/heroes",
    alt_text="Mountain landscape at sunset",
    title="Hero Image - Mountains"
)

3. Creating Multi-Locale Content

Goal: Create a blog post with English and German translations.

Prompt to Claude Code:

"Create a new blog post titled 'Welcome to Our Blog' in English and 'Willkommen in unserem Blog' in German"

Claude Code executes:

# Step 1: Create content item
result = create_content(
    collection_slug="blog-posts",
    description="Welcome blog post",
    status="draft"
)
content_item_id = result["id"]

# Step 2: Add English translation
update_content_translation(
    content_item_id=content_item_id,
    locale="en-US",
    data={
        "title": "Welcome to Our Blog",
        "slug": "welcome",
        "content": "# Welcome\n\nThanks for visiting our blog..."
    }
)

# Step 3: Add German translation
update_content_translation(
    content_item_id=content_item_id,
    locale="de-DE",
    data={
        "title": "Willkommen in unserem Blog",
        "slug": "willkommen",
        "content": "# Willkommen\n\nDanke für Ihren Besuch in unserem Blog..."
    }
)

4. Schema Modifications

Goal: Add a new "tags" field to an existing collection and reorder fields.

Prompt to Claude Code:

"Add a 'tags' text field to the blog-posts collection, then reorder fields so tags appears after title"

Claude Code executes:

# Step 1: Check current schema
schema = collections(slug="blog-posts")
print(f"Current fields: {[f['field_name'] for f in schema['collection']['fields']]}")

# Step 2: Add tags field
add_collection_field(
    collection_slug="blog-posts",
    name="tags",
    field_type="text",
    interface_type="input",
    is_required=False
)

# Step 3: Reorder fields
reorder_collection_fields(
    collection_slug="blog-posts",
    field_names=["title", "tags", "slug", "content", "author", "published_date"]
)

5. Content Queries and Updates

Goal: Find all draft blog posts and publish them.

Prompt to Claude Code:

"List all draft blog posts and publish them"

Claude Code executes:

# Step 1: Get all blog posts
result = content(collection_slug="blog-posts")

# Step 2: Filter draft items
draft_items = [item for item in result["items"] if item["status"] == "draft"]

print(f"Found {len(draft_items)} draft posts")

# Step 3: Publish each draft
for item in draft_items:
    # Get full item with translations
    full_item = content(
        collection_slug="blog-posts",
        content_item_id=item["id"]
    )
    
    # Update each translation to published status
    for translation in full_item["translations"]:
        update_content_translation(
            content_item_id=item["id"],
            locale=translation["locale"],
            data=translation["data"],
            status="published"
        )
    
    print(f"Published: {item['description']}")

Advanced Patterns

Batch Operations

When performing multiple operations, optimize by batching related calls:

Anti-pattern (slow):

for field_name in field_names:
    add_collection_field(collection_slug, field_name, ...)
    # Wait for response
    # Next iteration

Best practice (fast):

# Prepare all field configurations
fields = [
    {"name": "title", "field_type": "text", "interface_type": "input"},
    {"name": "content", "field_type": "markdown", "interface_type": "markdown"},
    # ...
]

# Execute in sequence (Claude Code handles this automatically)
for field in fields:
    add_collection_field(collection_slug="blog", **field)

Complex Content Creation

Pattern: Content with file references

# Step 1: Upload referenced files first
hero_image = upload_file_from_url(
    url="https://example.com/hero.jpg",
    folder_path="/images/posts",
    alt_text="Post hero image"
)

# Step 2: Create content with file ID reference
content_item = create_content(
    collection_slug="blog-posts",
    description="New post with hero image"
)

# Step 3: Add translation with file reference
update_content_translation(
    content_item_id=content_item["id"],
    locale="en-US",
    data={
        "title": "My Post",
        "content": "Post content...",
        "hero_image": hero_image["id"]  # Reference uploaded file
    }
)

Multi-Locale Content Management

Pattern: Bulk translation creation

# Define content in multiple locales
translations = {
    "en-US": {"title": "Welcome", "content": "Welcome to our site..."},
    "de-DE": {"title": "Willkommen", "content": "Willkommen auf unserer Seite..."},
    "fr-FR": {"title": "Bienvenue", "content": "Bienvenue sur notre site..."},
    "es-ES": {"title": "Bienvenido", "content": "Bienvenido a nuestro sitio..."}
}

# Create content item
item = create_content(
    collection_slug="pages",
    description="Homepage",
    status="draft"
)

# Add all translations
for locale, data in translations.items():
    update_content_translation(
        content_item_id=item["id"],
        locale=locale,
        data=data
    )

Automated Content Generation

Pattern: Generate content from external data

# External data source (e.g., team members from API)
team_data = [
    {"name": "Alice Johnson", "role": "CEO", "bio": "Founder with 15 years experience..."},
    {"name": "Bob Smith", "role": "CTO", "bio": "Tech lead specializing in..."},
    # ...
]

# Generate content items for each team member
for member in team_data:
    # Create content item
    item = create_content(
        collection_slug="team",
        description=f"{member['name']} - {member['role']}"
    )
    
    # Add content data
    update_content_translation(
        content_item_id=item["id"],
        locale="en-US",
        data={
            "name": member["name"],
            "role": member["role"],
            "bio": member["bio"]
        }
    )
    
    print(f"Created profile for {member['name']}")

Integration with Other Systems

Pattern: Sync content from external CMS

# Fetch content from external API
import requests
external_posts = requests.get("https://api.external-cms.com/posts").json()

# Sync to Vibe CMS
for post in external_posts:
    # Create content item
    item = create_content(
        collection_slug="blog-posts",
        description=post["title"],
        status="published"
    )
    
    # Download and upload featured image
    if post.get("featured_image_url"):
        image = upload_file_from_url(
            url=post["featured_image_url"],
            folder_path="/images/posts",
            alt_text=post.get("image_alt", "")
        )
        featured_image_id = image["id"]
    else:
        featured_image_id = None
    
    # Add content with image reference
    update_content_translation(
        content_item_id=item["id"],
        locale="en-US",
        data={
            "title": post["title"],
            "slug": post["slug"],
            "content": post["content"],
            "author": post["author"],
            "published_date": post["published_at"],
            "featured_image": featured_image_id
        }
    )
    
    print(f"Synced: {post['title']}")

Troubleshooting

"Authentication required"

Problem: MCP endpoint returns 401 error.

Solutions:

  • Verify API key is included in headers (X-API-Key or Authorization: Bearer)
  • Check API key is valid and not expired
  • Ensure key was created for correct project
  • Regenerate API key if needed

"Invalid or expired API key"

Problem: Tool execution fails with authentication error.

Solutions:

  • Generate new API key via UI
  • Verify key hasn't been deleted
  • Check key expiration date
  • Ensure key is for correct project

"Permission denied"

Problem: API key cannot access resources.

Solutions:

  • Verify API key is scoped to correct project
  • Check if collection belongs to API key's project
  • Review database RLS policies
  • Check database logs for detailed error

Tools not appearing in Claude Code

Problem: Hammer icon missing or tools not listed.

Solutions:

  • Restart Claude Code completely
  • Check MCP configuration file syntax (valid JSON)
  • Verify URL has trailing slash (/mcp/)
  • Test endpoint: curl -H "X-API-Key: sk_..." https://your-cms.com/mcp/
  • Check backend logs for authentication attempts

"Field name validation failed"

Problem: Cannot create field with desired name.

Solutions:

  • Use lowercase letters only (not camelCase or PascalCase)
  • Start field name with letter (not number or underscore)
  • Avoid reserved words (id, created_at, select, from, where, etc.)
  • Use underscores instead of hyphens or spaces

"Interface not compatible with field type"

Problem: Cannot set interface type for field.

Solutions:

  • Check field type compatibility:
    • text → input, textarea
    • markdown → markdown
    • number → input
    • boolean → input
    • file → single_file, multiple_files
  • Cannot change field type after creation
  • Delete and recreate field if type change needed

Next Steps

Additional Resources

Advanced Topics

Phase 2: Enhanced Read Operations

  • Advanced filtering and search
  • Pagination for large datasets
  • Aggregation queries
  • Full-text search

Phase 3: Workflow Automation

  • Content review and approval workflows
  • Scheduled publishing
  • Automated translations
  • SEO optimization

Phase 4: Integration Features

  • Webhooks for content changes
  • Export/import operations
  • Backup and restore
  • Analytics and reporting

Questions or Issues?

If you encounter problems or have questions about the MCP server:

  1. Check the Troubleshooting section above
  2. Review backend logs for detailed error messages
  3. Consult the FastMCP documentation for framework-specific issues
  4. Open an issue on the Vibe CMS GitHub repository

This documentation reflects MCP Server v2.0 with database-layer security and zero-trust architecture.