ghost-mcp/contracts/content-formats.md

8.8 KiB

Ghost Content Formats Documentation

Overview

Ghost supports multiple content formats for creating and managing posts and pages. Understanding these formats is crucial for proper MCP tool implementation.

Content Format Evolution

Historical Context

  1. Mobiledoc (Legacy): Ghost's previous JSON-based content format
  2. HTML: Traditional markup format
  3. Lexical (Current): Ghost's current standardized JSON content format

Current Content Formats

1. Lexical Format (Primary)

Status: Current standard format Type: JSON-based structured content Usage: Default format for all new content

Structure

{
  "lexical": "{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Welcome to Ghost!\",\"type\":\"text\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}"
}

Benefits

  • Rich content representation
  • Structured, parseable format
  • Supports complex layouts and content blocks
  • Platform-agnostic content storage

2. HTML Format (Optional)

Status: Available for output/input Type: Rendered HTML markup Usage: For compatibility and direct HTML input

Structure

{
  "html": "<p>Welcome to Ghost!</p><p>This is a simple HTML paragraph.</p>"
}

Use Cases

  • Content migration from HTML-based systems
  • Direct HTML content creation
  • Output for web rendering

3. Mobiledoc Format (Legacy)

Status: Legacy support (deprecated) Type: JSON-based (older format) Usage: Existing content only

Note: New content should use Lexical format. Mobiledoc is maintained for backward compatibility.

API Content Handling

Content Retrieval

Default Behavior

  • API returns Lexical format by default
  • HTML format requires explicit request

Format Selection

// Get only Lexical (default)
GET /ghost/api/content/posts/

// Get both Lexical and HTML
GET /ghost/api/content/posts/?formats=html,lexical

// Get only HTML
GET /ghost/api/content/posts/?formats=html

Response Examples

Lexical Only (Default):

{
  "posts": [
    {
      "id": "post_id",
      "title": "My Post",
      "lexical": "{\"root\":{...}}",
      "slug": "my-post"
    }
  ]
}

HTML and Lexical:

{
  "posts": [
    {
      "id": "post_id",
      "title": "My Post",
      "lexical": "{\"root\":{...}}",
      "html": "<p>Post content</p>",
      "slug": "my-post"
    }
  ]
}

Content Creation & Updates

Admin API Content Fields

// Creating a post with Lexical
const postData = {
  posts: [{
    title: "New Post",
    lexical: JSON.stringify(lexicalContent),
    status: "draft"
  }]
};

// Creating a post with HTML
const postData = {
  posts: [{
    title: "New Post",
    html: "<p>HTML content here</p>",
    status: "draft"
  }]
};

// Creating with both (Lexical takes precedence)
const postData = {
  posts: [{
    title: "New Post",
    lexical: JSON.stringify(lexicalContent),
    html: "<p>Fallback HTML</p>",
    status: "draft"
  }]
};

Content Format Priorities

Create/Update Priority Order

  1. Lexical (highest priority)
  2. HTML (fallback if no Lexical)
  3. Mobiledoc (legacy fallback)

Conversion Behavior

  • HTML → Lexical: Ghost converts HTML to Lexical automatically
  • Lexical → HTML: Ghost renders Lexical to HTML
  • Mobiledoc → Lexical: Ghost migrates existing Mobiledoc content

Implementation for MCP Server

Content Format Detection

class GhostContentHandler {
  detectContentFormat(content) {
    if (typeof content === 'object' && content.root) {
      return 'lexical';
    }
    if (typeof content === 'string' && content.startsWith('<')) {
      return 'html';
    }
    if (typeof content === 'object' && content.version) {
      return 'mobiledoc';
    }
    return 'unknown';
  }

  prepareContentForAPI(content, preferredFormat = 'lexical') {
    const format = this.detectContentFormat(content);

    switch (format) {
      case 'lexical':
        return {
          lexical: typeof content === 'string' ? content : JSON.stringify(content)
        };
      case 'html':
        return {
          html: content
        };
      case 'mobiledoc':
        return {
          mobiledoc: typeof content === 'string' ? content : JSON.stringify(content)
        };
      default:
        // Assume plain text, wrap in HTML
        return {
          html: `<p>${content}</p>`
        };
    }
  }
}

MCP Tool Content Parameters

Create Post Tool

const createPostTool = {
  name: "ghost_admin_create_post",
  parameters: {
    title: { type: "string", required: true },

    // Content format options (one required)
    lexical: { type: "string", description: "Lexical JSON content" },
    html: { type: "string", description: "HTML content" },
    mobiledoc: { type: "string", description: "Mobiledoc JSON content (legacy)" },

    // Other parameters...
    status: { type: "string", enum: ["draft", "published"] }
  }
};

Content Validation

function validateContentInput(params) {
  const contentFormats = ['lexical', 'html', 'mobiledoc'].filter(
    format => params[format] !== undefined
  );

  if (contentFormats.length === 0) {
    throw new Error('At least one content format (lexical, html, or mobiledoc) is required');
  }

  if (contentFormats.length > 1) {
    console.warn('Multiple content formats provided. Lexical will take precedence.');
  }

  // Validate Lexical JSON if provided
  if (params.lexical) {
    try {
      JSON.parse(params.lexical);
    } catch (error) {
      throw new Error('Invalid Lexical JSON format');
    }
  }

  return true;
}

Content Format Examples

1. Simple Lexical Content

{
  "root": {
    "children": [
      {
        "children": [
          {
            "detail": 0,
            "format": 0,
            "mode": "normal",
            "style": "",
            "text": "Hello World!",
            "type": "text",
            "version": 1
          }
        ],
        "direction": "ltr",
        "format": "",
        "indent": 0,
        "type": "paragraph",
        "version": 1
      }
    ],
    "direction": "ltr",
    "format": "",
    "indent": 0,
    "type": "root",
    "version": 1
  }
}

2. Rich HTML Content

<h1>My Blog Post</h1>
<p>This is a <strong>rich</strong> HTML post with <em>formatting</em>.</p>
<ul>
  <li>First item</li>
  <li>Second item</li>
</ul>
<blockquote>
  <p>This is a quote block.</p>
</blockquote>

3. Content Conversion Utility

class ContentConverter {
  // Convert HTML to simple Lexical
  htmlToLexical(html) {
    // Basic implementation - in practice, use Ghost's conversion utilities
    const paragraphs = html.split('</p>').filter(p => p.trim());
    const children = paragraphs.map(p => {
      const text = p.replace(/<[^>]*>/g, '').trim();
      return {
        children: [{
          detail: 0,
          format: 0,
          mode: "normal",
          style: "",
          text: text,
          type: "text",
          version: 1
        }],
        direction: "ltr",
        format: "",
        indent: 0,
        type: "paragraph",
        version: 1
      };
    });

    return {
      root: {
        children: children,
        direction: "ltr",
        format: "",
        indent: 0,
        type: "root",
        version: 1
      }
    };
  }

  // Convert Lexical to simple HTML
  lexicalToHtml(lexical) {
    const content = typeof lexical === 'string' ? JSON.parse(lexical) : lexical;
    const paragraphs = content.root.children.map(child => {
      if (child.type === 'paragraph') {
        const text = child.children.map(textNode => textNode.text).join('');
        return `<p>${text}</p>`;
      }
      return '';
    });
    return paragraphs.join('\n');
  }
}

Best Practices

For MCP Implementation

  1. Default to Lexical: Use Lexical format for new content creation
  2. Support HTML Input: Allow HTML for ease of use and migration
  3. Validate JSON: Always validate Lexical JSON before sending to API
  4. Handle Conversion: Provide utilities for format conversion if needed
  5. Graceful Fallback: Handle legacy Mobiledoc content gracefully

Content Creation Guidelines

  1. Use Lexical for Rich Content: Complex layouts, cards, embeds
  2. Use HTML for Simple Content: Basic text formatting
  3. Provide Format Options: Let users choose their preferred input format
  4. Validate Before Submission: Check content validity before API calls

Implementation Status

  • Content formats identified and documented
  • API handling patterns documented
  • Implementation strategy planned
  • Validation approaches defined
  • Content conversion utilities pending implementation
  • Real format testing pending API access