From Python to TypeScript: Improving Ghost MCP Server
From Python to TypeScript: How rebuilding my Ghost MCP server with official libraries eliminated custom code complexity, improved stability, and streamlined deployment—making AI-powered content management more reliable for creators.

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.
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:
- Write JWT token generation code for API authentication, including handling the specific format of Ghost's API keys and generating properly formatted tokens
- Create custom HTTP request handlers for each API endpoint
- Build entity models from scratch
- Develop error handling mechanisms specific to the Ghost API
- 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:
- Leveraging official client libraries: Instead of writing custom authentication and request handling, I could use
@tryghost/admin-api
which already implements these correctly - TypeScript type safety: Stronger typing reduced errors and improved development experience
- Simplified architecture: The codebase became more concise and focused
- Better maintainability: Official libraries are maintained by the Ghost team, so I benefit from their updates
- 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:
- Clone the repository
- Set up a Python environment
- Install dependencies
- Configure environment variables
- 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:


Discussion