Ghost MCP Server
by MFYDev
Verified
"""Newsletter-related MCP tools for Ghost API."""
import json
from mcp.server.fastmcp import Context
from ..api import make_ghost_request, get_auth_headers
from ..config import STAFF_API_KEY
from ..exceptions import GhostError
async def list_newsletters(
format: str = "text",
page: int = 1,
limit: int = 15,
ctx: Context = None
) -> str:
"""Get the list of newsletters from your Ghost blog.
Args:
format: Output format - either "text" or "json" (default: "text")
page: Page number for pagination (default: 1)
limit: Number of newsletters per page (default: 15)
ctx: Optional context for logging
Returns:
Formatted string containing newsletter information
"""
if ctx:
ctx.info(f"Listing newsletters (page {page}, limit {limit}, format {format})")
try:
if ctx:
ctx.debug("Getting auth headers")
headers = await get_auth_headers(STAFF_API_KEY)
if ctx:
ctx.debug("Making API request to /newsletters/ with pagination")
data = await make_ghost_request(
f"newsletters/?page={page}&limit={limit}",
headers,
ctx
)
if ctx:
ctx.debug("Processing newsletters list response")
newsletters = data.get("newsletters", [])
if not newsletters:
if ctx:
ctx.info("No newsletters found in response")
return "No newsletters found."
if format.lower() == "json":
if ctx:
ctx.debug("Returning JSON format")
return json.dumps(newsletters, indent=2)
formatted_newsletters = []
for newsletter in newsletters:
formatted_newsletter = f"""
Name: {newsletter.get('name', 'Unknown')}
Description: {newsletter.get('description', 'No description')}
Status: {newsletter.get('status', 'Unknown')}
Visibility: {newsletter.get('visibility', 'Unknown')}
Subscribe on Signup: {newsletter.get('subscribe_on_signup', False)}
ID: {newsletter.get('id', 'Unknown')}
"""
formatted_newsletters.append(formatted_newsletter)
return "\n---\n".join(formatted_newsletters)
except GhostError as e:
if ctx:
ctx.error(f"Failed to list newsletters: {str(e)}")
return str(e)
async def read_newsletter(newsletter_id: str, ctx: Context = None) -> str:
"""Get the details of a specific newsletter.
Args:
newsletter_id: The ID of the newsletter to retrieve
ctx: Optional context for logging
Returns:
Formatted string containing the newsletter details
"""
if ctx:
ctx.info(f"Reading newsletter details for ID: {newsletter_id}")
try:
if ctx:
ctx.debug("Getting auth headers")
headers = await get_auth_headers(STAFF_API_KEY)
if ctx:
ctx.debug(f"Making API request to /newsletters/{newsletter_id}/")
data = await make_ghost_request(
f"newsletters/{newsletter_id}/",
headers,
ctx
)
if ctx:
ctx.debug("Processing newsletter response data")
newsletter = data["newsletters"][0]
return f"""
Name: {newsletter.get('name', 'Unknown')}
Description: {newsletter.get('description', 'No description')}
Status: {newsletter.get('status', 'Unknown')}
Visibility: {newsletter.get('visibility', 'Unknown')}
Subscribe on Signup: {newsletter.get('subscribe_on_signup', False)}
Sort Order: {newsletter.get('sort_order', 0)}
Sender Email: {newsletter.get('sender_email', 'Not set')}
Sender Reply To: {newsletter.get('sender_reply_to', 'Not set')}
Show Header Icon: {newsletter.get('show_header_icon', True)}
Show Header Title: {newsletter.get('show_header_title', True)}
Show Header Name: {newsletter.get('show_header_name', True)}
Show Feature Image: {newsletter.get('show_feature_image', True)}
Title Font Category: {newsletter.get('title_font_category', 'Unknown')}
Body Font Category: {newsletter.get('body_font_category', 'Unknown')}
Show Badge: {newsletter.get('show_badge', True)}
Created: {newsletter.get('created_at', 'Unknown')}
Updated: {newsletter.get('updated_at', 'Unknown')}
"""
except GhostError as e:
if ctx:
ctx.error(f"Failed to read newsletter: {str(e)}")
return str(e)
async def create_newsletter(
name: str,
description: str = None,
status: str = "active",
subscribe_on_signup: bool = True,
opt_in_existing: bool = False,
sender_reply_to: str = "newsletter",
show_header_icon: bool = True,
show_header_title: bool = True,
show_header_name: bool = True,
show_feature_image: bool = True,
title_font_category: str = "sans_serif",
title_alignment: str = "center",
body_font_category: str = "sans_serif",
show_badge: bool = True,
ctx: Context = None
) -> str:
"""Create a new newsletter.
Args:
name: Name of the newsletter (required)
description: Newsletter description
status: Newsletter status ("active" or "archived")
subscribe_on_signup: Whether to subscribe new members automatically
opt_in_existing: Whether to subscribe existing members
sender_reply_to: Reply-to setting ("newsletter" or "support")
show_header_icon: Whether to show header icon
show_header_title: Whether to show header title
show_header_name: Whether to show header name
show_feature_image: Whether to show feature image
title_font_category: Font category for titles
title_alignment: Title alignment
body_font_category: Font category for body text
show_badge: Whether to show badge
ctx: Optional context for logging
Returns:
Formatted string containing the created newsletter details
"""
if ctx:
ctx.info(f"Creating new newsletter: {name}")
try:
if ctx:
ctx.debug("Getting auth headers")
headers = await get_auth_headers(STAFF_API_KEY)
newsletter_data = {
"newsletters": [{
"name": name,
"description": description,
"status": status,
"subscribe_on_signup": subscribe_on_signup,
"sender_reply_to": sender_reply_to,
"show_header_icon": show_header_icon,
"show_header_title": show_header_title,
"show_header_name": show_header_name,
"show_feature_image": show_feature_image,
"title_font_category": title_font_category,
"title_alignment": title_alignment,
"body_font_category": body_font_category,
"show_badge": show_badge
}]
}
if ctx:
ctx.debug("Making API request to create newsletter")
endpoint = f"newsletters/?opt_in_existing={'true' if opt_in_existing else 'false'}"
data = await make_ghost_request(
endpoint,
headers,
ctx,
http_method="POST",
json_data=newsletter_data
)
if ctx:
ctx.debug("Processing create newsletter response")
newsletter = data["newsletters"][0]
return f"""
Newsletter created successfully!
Name: {newsletter.get('name')}
Description: {newsletter.get('description', 'No description')}
Status: {newsletter.get('status')}
ID: {newsletter.get('id')}
"""
except GhostError as e:
if ctx:
ctx.error(f"Failed to create newsletter: {str(e)}")
return str(e)
async def update_newsletter(
newsletter_id: str,
name: str = None,
description: str = None,
sender_name: str = None,
sender_email: str = None,
sender_reply_to: str = None,
status: str = None,
subscribe_on_signup: bool = None,
sort_order: int = None,
header_image: str = None,
show_header_icon: bool = None,
show_header_title: bool = None,
show_header_name: bool = None,
title_font_category: str = None,
title_alignment: str = None,
show_feature_image: bool = None,
body_font_category: str = None,
footer_content: str = None,
show_badge: bool = None,
ctx: Context = None
) -> str:
"""Update an existing newsletter.
Args:
newsletter_id: ID of the newsletter to update (required)
name: New newsletter name
description: New newsletter description
sender_name: Name shown in email clients
sender_email: Email address newsletters are sent from
sender_reply_to: Reply-to setting ("newsletter" or "support")
status: Newsletter status ("active" or "archived")
subscribe_on_signup: Whether to subscribe new members automatically
sort_order: Order in lists
header_image: URL of header image
show_header_icon: Whether to show header icon
show_header_title: Whether to show header title
show_header_name: Whether to show header name
title_font_category: Font category for titles
title_alignment: Title alignment
show_feature_image: Whether to show feature image
body_font_category: Font category for body text
footer_content: Custom footer content
show_badge: Whether to show badge
ctx: Optional context for logging
Returns:
Formatted string containing the updated newsletter details
"""
if ctx:
ctx.info(f"Updating newsletter with ID: {newsletter_id}")
try:
if ctx:
ctx.debug("Getting auth headers")
headers = await get_auth_headers(STAFF_API_KEY)
# Build update data with only provided fields
update_data = {"newsletters": [{"id": newsletter_id}]}
# Add non-None values to the update data
fields = locals()
for field in [
"name", "description", "sender_name", "sender_email",
"sender_reply_to", "status", "subscribe_on_signup",
"sort_order", "header_image", "show_header_icon",
"show_header_title", "show_header_name", "title_font_category",
"title_alignment", "show_feature_image", "body_font_category",
"footer_content", "show_badge"
]:
if fields[field] is not None:
update_data["newsletters"][0][field] = fields[field]
if ctx:
ctx.debug(f"Making API request to update newsletter {newsletter_id}")
data = await make_ghost_request(
f"newsletters/{newsletter_id}/",
headers,
ctx,
http_method="PUT",
json_data=update_data
)
if ctx:
ctx.debug("Processing update newsletter response")
newsletter = data["newsletters"][0]
return f"""
Newsletter updated successfully!
Name: {newsletter.get('name')}
Description: {newsletter.get('description', 'No description')}
Status: {newsletter.get('status')}
Sender Name: {newsletter.get('sender_name', 'Not set')}
Sender Email: {newsletter.get('sender_email', 'Not set')}
Sort Order: {newsletter.get('sort_order', 0)}
ID: {newsletter.get('id')}
"""
except GhostError as e:
if ctx:
ctx.error(f"Failed to update newsletter: {str(e)}")
return str(e)