When building tools for content creators, reliability and stability are paramount. As a developer working with Ghost CMS, I wanted to create a seamless integration between large language models (LLMs) like Claude and my content management system. This led me to build a Model Context Protocol (MCP) server for Ghost - a bridge that allows AI assistants to interact with my Ghost blog.

My initial implementation used Python, which worked but had limitations. After months of usage, I decided to completely rewrite the project in TypeScript. This post explores why I made this decision and the significant improvements that resulted from it.

Release v0.1.0 · MFYDev/ghost-mcp
Ghost MCP Server v0.1.0 Release Notes I’m excited to announce the release of my Ghost MCP v0.1.0, now completely rewritten in TypeScript! This release marks a significant milestone in my project’s…

The Original Python Implementation

My first Ghost MCP server was built in Python and used custom HTTP request handling to interact with the Ghost Admin API. I had to:

  1. Write JWT token generation code for API authentication, including handling the specific format of Ghost's API keys and generating properly formatted tokens
  2. Create custom HTTP request handlers for each API endpoint
  3. Build entity models from scratch
  4. Develop error handling mechanisms specific to the Ghost API
  5. Implement resource and tool layers that could translate between MCP requests and Ghost API responses

This involved manually crafting raw HTTP requests for every single endpoint - posts, members, tags, users, and more. I had to implement the specific JWT authentication flow that the Ghost Admin API requires, including generating tokens and managing headers correctly.

While functional, this approach had several drawbacks:

  • Stability issues: My custom HTTP layer sometimes failed in unpredictable ways, especially when dealing with the complexities of the Lexical editor format
  • Implementation burden: I had to reimplement logic that already existed in official libraries
  • Maintenance overhead: Each Ghost API update required me to adapt my custom implementation, taking time away from developing core MCP features

The TypeScript Rewrite

As the project evolved, the maintenance overhead of this custom Python API layer started to feel like a significant bottleneck. It was taking time away from developing the core MCP features.

After experiencing these challenges, I discovered the official @tryghost/admin-api Node.js client. This revelation was a true turning point - the library was essentially a pre-built, robust version of what I had spent considerable time constructing manually in Python.

This prompted me to rewrite my entire server in TypeScript. The benefits became immediately apparent:

  1. Leveraging official client libraries: Instead of writing custom authentication and request handling, I could use @tryghost/admin-api which already implements these correctly
  2. TypeScript type safety: Stronger typing reduced errors and improved development experience
  3. Simplified architecture: The codebase became more concise and focused
  4. Better maintainability: Official libraries are maintained by the Ghost team, so I benefit from their updates
  5. Complete API coverage: I gained access to the full range of Ghost Admin API features without additional implementation effort

Key Improvements

Configuration and Setup

In the Python version, I had to configure JWT token generation and handle API requests manually:

async def generate_token(staff_api_key: str, audience: str = "/admin/") -> str:
    """Generate a JWT token for Ghost Admin API authentication."""
    try:
        key_id, secret = staff_api_key.split(":")
    except ValueError:
        raise ValueError("STAFF_API_KEY must be in the format 'id:secret'") 
    # ... more complex code ...

With TypeScript, initialization became much simpler:

// Initialize and export the Ghost Admin API client instance
export const ghostApiClient = new GhostAdminAPI({
    url: GHOST_API_URL,
    key: GHOST_ADMIN_API_KEY,
    version: GHOST_API_VERSION
});

Tool Implementation

The API tools for managing posts, members, and other entities are much cleaner in TypeScript. For example, here's a comparison of creating posts:

Python (complex and verbose):

async def create_post(post_data: dict, ctx: Context = None) -> str:
    """Create a new blog post."""
    if not isinstance(post_data, dict):
        error_msg = "post_data must be a dictionary"
        if ctx:
            ctx.error(error_msg)
        return error_msg

    # ... many lines of validation and preparation ...
    
    try:
        # Get auth headers, make request, etc.
        # ... more code ...
    except Exception as e:
        if ctx:
            ctx.error(f"Failed to create post: {str(e)}")
        raise

TypeScript (clean and simple):

// Add post
server.tool(
  "posts_add",
  addParams,
  async (args, _extra) => {
    // If html is present, use source: "html" to ensure Ghost uses the html content
    const options = args.html ? { source: "html" } : undefined;
    const post = await ghostApiClient.posts.add(args, options);
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(post, null, 2),
        },
      ],
    };
  }
);

The difference is stark. Instead of the multi-step process in Python – manually constructing a request, generating a JWT, setting headers, sending the request, and parsing the response – the TypeScript version is reduced to a single, clean line: await ghostApiClient.posts.add(args, options). This simplification echoes across the entire API interaction layer, dramatically streamlining the codebase.

Lessons Learned

1. Use Official Libraries When Available

One of the biggest lessons from this project was recognizing when to build custom solutions versus using official libraries. The Ghost Admin API client already handled authentication, request formatting, and error handling - all pain points in my Python implementation. This journey really highlighted the value of using official libraries and SDKs when they are available. They often save considerable development time upfront and drastically reduce the burden of ongoing maintenance.

2. Choose the Right Language for the Ecosystem

JavaScript/TypeScript has a thriving ecosystem for CMS integrations. By switching to TypeScript, I gained access to not just the Ghost client but also better tools for working with the MCP protocol itself. The overall developer experience feels smoother and more efficient when working with the official SDK.

3. Type Safety Matters

TypeScript's static typing caught numerous issues during development that would have been runtime errors in the Python version. For a server that needs to be reliable, this added safety was invaluable.

Deployment and Usage

The rewritten TypeScript version is not only more reliable but also easier to deploy and use. In the Python version, users needed to:

  1. Clone the repository
  2. Set up a Python environment
  3. Install dependencies
  4. Configure environment variables
  5. Run the server with specific commands

With the TypeScript version, installation is much simpler:

{
  "mcpServers": {
      "ghost-mcp": {
        "command": "npx",
        "args": ["-y", "@fanyangmeng/ghost-mcp"],
        "env": {
            "GHOST_API_URL": "https://yourblog.com",
            "GHOST_ADMIN_API_KEY": "your_admin_api_key",
            "GHOST_API_VERSION": "v5.0"
        }
      }
    }
}

Configuration is also streamlined through environment variables or a simple JSON config, making it accessible to less technical users.

Conclusion

Rewriting my Ghost MCP server from Python to TypeScript was a significant undertaking, but the benefits have far outweighed the costs. The server is now more stable, easier to maintain, and simpler to use.

The key takeaway is that when integrating with platforms like Ghost CMS, it's worth investigating whether official client libraries exist before building custom solutions. Sometimes the best code is the code you don't have to write.

If you're working with Ghost CMS and want to integrate it with LLMs, give my TypeScript MCP server a try. It provides a robust bridge between your content and AI assistants, making content management even more powerful.

If you find Ghost MCP Server useful, please consider:

  • ⭐ Starring the repo on GitHub
  • 🤝 Contributing code or documentation
  • ❤️ Sponsoring the project on GitHub Sponsors

Previous Related Content:

Introducing Ghost-MCP: A Model Context Protocol Server for Ghost CMS
Introducing Ghost-MCP, a groundbreaking integration between Ghost and Claude through (MCP) revolutionizes blog management. Now you can control your entire Ghost blog—from content creation to member management—using simple conversational commands. Transform your blogging workflow with AI.
Improving Code Structure in the Ghost MCP Server: A Journey Through Refactoring
Explore a comprehensive refactoring journey of the Ghost-MCP codebase, where modular design, dynamic tool discovery, and enhanced type safety transformed a monolithic structure into a maintainable, developer-friendly system. Learn practical strategies for Python project architecture.