From b44165a043f004fe772439873c27a2512d4a3c5e Mon Sep 17 00:00:00 2001 From: Fanyang Meng Date: Tue, 11 Feb 2025 22:40:57 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Support=20newsletter=20creating=20a?= =?UTF-8?q?nd=20updating?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + src/ghost_mcp/server.py | 2 + src/ghost_mcp/tools.py | 8 +- src/ghost_mcp/tools/__init__.py | 6 +- src/ghost_mcp/tools/newsletters.py | 200 +++++++++++++++++++++++++++++ 5 files changed, 214 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 90b71db..3357b70 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ GHOST_API_URL=your_ghost_api_url GHOST_STAFF_API_KEY=your_staff_api_key npx @mod ### Newsletters Management - `list_newsletters`: List all newsletters associated with the blog - `read_newsletter`: Retrieve detailed settings and information for a specific newsletter +- `create_newsletter`: Create a new newsletter with specified details +- `update_newsletter`: Update an existing newsletter with new information ## Available Resources diff --git a/src/ghost_mcp/server.py b/src/ghost_mcp/server.py index 8c2ba34..acb45c6 100644 --- a/src/ghost_mcp/server.py +++ b/src/ghost_mcp/server.py @@ -59,6 +59,8 @@ def create_server() -> FastMCP: mcp.tool()(tools.read_offer) mcp.tool()(tools.list_newsletters) mcp.tool()(tools.read_newsletter) + mcp.tool()(tools.create_newsletter) + mcp.tool()(tools.update_newsletter) # Register prompts @mcp.prompt() diff --git a/src/ghost_mcp/tools.py b/src/ghost_mcp/tools.py index dde152c..15dfb74 100644 --- a/src/ghost_mcp/tools.py +++ b/src/ghost_mcp/tools.py @@ -29,7 +29,9 @@ from .tools.offers import ( ) from .tools.newsletters import ( list_newsletters, - read_newsletter + read_newsletter, + create_newsletter, + update_newsletter ) __all__ = [ @@ -50,5 +52,7 @@ __all__ = [ 'list_offers', 'read_offer', 'list_newsletters', - 'read_newsletter' + 'read_newsletter', + 'create_newsletter', + 'update_newsletter' ] diff --git a/src/ghost_mcp/tools/__init__.py b/src/ghost_mcp/tools/__init__.py index 7154901..ec24d4b 100644 --- a/src/ghost_mcp/tools/__init__.py +++ b/src/ghost_mcp/tools/__init__.py @@ -5,7 +5,7 @@ from .users import list_users, read_user from .members import list_members, read_member from .tiers import list_tiers, read_tier, create_tier, update_tier from .offers import list_offers, read_offer -from .newsletters import list_newsletters, read_newsletter +from .newsletters import list_newsletters, read_newsletter, create_newsletter, update_newsletter __all__ = [ 'search_posts_by_title', @@ -25,5 +25,7 @@ __all__ = [ 'list_offers', 'read_offer', 'list_newsletters', - 'read_newsletter' + 'read_newsletter', + 'create_newsletter', + 'update_newsletter' ] diff --git a/src/ghost_mcp/tools/newsletters.py b/src/ghost_mcp/tools/newsletters.py index c1b365d..0925d44 100644 --- a/src/ghost_mcp/tools/newsletters.py +++ b/src/ghost_mcp/tools/newsletters.py @@ -126,3 +126,203 @@ Updated: {newsletter.get('updated_at', 'Unknown')} 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) \ No newline at end of file