Support tier creating and updating

This commit is contained in:
Fanyang Meng
2025-02-11 22:26:51 -05:00
parent 681aad611c
commit 146c3ffcfd
5 changed files with 215 additions and 2 deletions

View File

@@ -90,6 +90,8 @@ GHOST_API_URL=your_ghost_api_url GHOST_STAFF_API_KEY=your_staff_api_key npx @mod
### Tiers Management ### Tiers Management
- `list_tiers`: List all available membership tiers - `list_tiers`: List all available membership tiers
- `read_tier`: Retrieve detailed information about a specific tier, including benefits and pricing - `read_tier`: Retrieve detailed information about a specific tier, including benefits and pricing
- `create_tier`: Create a new membership tier with specified details
- `update_tier`: Update an existing tier with new information
### Offers Management ### Offers Management
- `list_offers`: List promotional offers with relevant details - `list_offers`: List promotional offers with relevant details

View File

@@ -53,6 +53,8 @@ def create_server() -> FastMCP:
mcp.tool()(tools.read_member) mcp.tool()(tools.read_member)
mcp.tool()(tools.list_tiers) mcp.tool()(tools.list_tiers)
mcp.tool()(tools.read_tier) mcp.tool()(tools.read_tier)
mcp.tool()(tools.create_tier)
mcp.tool()(tools.update_tier)
mcp.tool()(tools.list_offers) mcp.tool()(tools.list_offers)
mcp.tool()(tools.read_offer) mcp.tool()(tools.read_offer)
mcp.tool()(tools.list_newsletters) mcp.tool()(tools.list_newsletters)

View File

@@ -19,7 +19,9 @@ from .tools.members import (
) )
from .tools.tiers import ( from .tools.tiers import (
list_tiers, list_tiers,
read_tier read_tier,
create_tier,
update_tier
) )
from .tools.offers import ( from .tools.offers import (
list_offers, list_offers,
@@ -43,6 +45,8 @@ __all__ = [
'read_member', 'read_member',
'list_tiers', 'list_tiers',
'read_tier', 'read_tier',
'create_tier',
'update_tier',
'list_offers', 'list_offers',
'read_offer', 'read_offer',
'list_newsletters', 'list_newsletters',

View File

@@ -3,7 +3,7 @@
from .posts import search_posts_by_title, list_posts, read_post, create_post, update_post, delete_post from .posts import search_posts_by_title, list_posts, read_post, create_post, update_post, delete_post
from .users import list_users, read_user from .users import list_users, read_user
from .members import list_members, read_member from .members import list_members, read_member
from .tiers import list_tiers, read_tier from .tiers import list_tiers, read_tier, create_tier, update_tier
from .offers import list_offers, read_offer from .offers import list_offers, read_offer
from .newsletters import list_newsletters, read_newsletter from .newsletters import list_newsletters, read_newsletter
@@ -20,6 +20,8 @@ __all__ = [
'read_member', 'read_member',
'list_tiers', 'list_tiers',
'read_tier', 'read_tier',
'create_tier',
'update_tier',
'list_offers', 'list_offers',
'read_offer', 'read_offer',
'list_newsletters', 'list_newsletters',

View File

@@ -1,6 +1,11 @@
"""Tier-related MCP tools for Ghost API.""" """Tier-related MCP tools for Ghost API."""
import json import json
from typing import Optional, List
from mcp.server.fastmcp import Context
from ..api import make_ghost_request, get_auth_headers
from ..config import STAFF_API_KEY
from mcp.server.fastmcp import Context from mcp.server.fastmcp import Context
from ..api import make_ghost_request, get_auth_headers from ..api import make_ghost_request, get_auth_headers
@@ -125,3 +130,201 @@ Benefits:
if ctx: if ctx:
ctx.error(f"Failed to read tier: {str(e)}") ctx.error(f"Failed to read tier: {str(e)}")
return str(e) return str(e)
async def create_tier(
name: str,
monthly_price: Optional[int] = None,
yearly_price: Optional[int] = None,
description: Optional[str] = None,
benefits: Optional[List[str]] = None,
welcome_page_url: Optional[str] = None,
visibility: str = "public",
currency: str = "usd",
ctx: Context = None
) -> str:
"""Create a new tier in Ghost.
Args:
name: Name of the tier (required)
monthly_price: Optional monthly price in cents (e.g. 500 for $5.00)
yearly_price: Optional yearly price in cents (e.g. 5000 for $50.00)
description: Optional description of the tier
benefits: Optional list of benefits for the tier
welcome_page_url: Optional URL for the welcome page
visibility: Visibility of tier, either "public" or "none" (default: "public")
currency: Currency for prices (default: "usd")
ctx: Optional context for logging
Returns:
String representation of the created tier
Raises:
GhostError: If the Ghost API request fails
"""
if not name:
raise ValueError("Name is required for creating a tier")
if ctx:
ctx.info(f"Creating new tier: {name}")
# Construct tier data
tier_data = {
"tiers": [{
"name": name,
"description": description,
"type": "paid" if (monthly_price or yearly_price) else "free",
"active": True,
"visibility": visibility,
"welcome_page_url": welcome_page_url,
"benefits": benefits or [],
"currency": currency
}]
}
# Add pricing if provided
if monthly_price is not None:
tier_data["tiers"][0]["monthly_price"] = monthly_price
if yearly_price is not None:
tier_data["tiers"][0]["yearly_price"] = yearly_price
try:
if ctx:
ctx.debug("Getting auth headers")
headers = await get_auth_headers(STAFF_API_KEY)
if ctx:
ctx.debug("Making API request to create tier")
response = await make_ghost_request(
"tiers/",
headers,
ctx,
http_method="POST",
json_data=tier_data
)
if ctx:
ctx.debug("Processing created tier response")
tier = response.get("tiers", [{}])[0]
# Format response
benefits_text = "\n- ".join(tier.get('benefits', [])) if tier.get('benefits') else "None"
return f"""
Tier created successfully:
Name: {tier.get('name')}
Type: {tier.get('type')}
Description: {tier.get('description', 'No description')}
Active: {tier.get('active', False)}
Visibility: {tier.get('visibility', 'public')}
Monthly Price: {tier.get('monthly_price', 'N/A')} {tier.get('currency', 'usd').upper()}
Yearly Price: {tier.get('yearly_price', 'N/A')} {tier.get('currency', 'usd').upper()}
Currency: {tier.get('currency', 'usd').upper()}
Benefits:
- {benefits_text}
ID: {tier.get('id', 'Unknown')}
"""
except Exception as e:
if ctx:
ctx.error(f"Failed to create tier: {str(e)}")
raise
async def update_tier(
tier_id: str,
name: Optional[str] = None,
description: Optional[str] = None,
monthly_price: Optional[int] = None,
yearly_price: Optional[int] = None,
benefits: Optional[List[str]] = None,
welcome_page_url: Optional[str] = None,
visibility: Optional[str] = None,
currency: Optional[str] = None,
active: Optional[bool] = None,
ctx: Context = None
) -> str:
"""Update an existing tier in Ghost.
Args:
tier_id: ID of the tier to update (required)
name: New name for the tier
description: New description for the tier
monthly_price: New monthly price in cents (e.g. 500 for $5.00)
yearly_price: New yearly price in cents (e.g. 5000 for $50.00)
benefits: New list of benefits for the tier
welcome_page_url: New URL for the welcome page
visibility: New visibility setting ("public" or "none")
currency: New currency for prices
active: New active status
ctx: Optional context for logging
Returns:
String representation of the updated tier
Raises:
GhostError: If the Ghost API request fails
"""
if ctx:
ctx.info(f"Updating tier with ID: {tier_id}")
# Construct update data with only provided fields
update_data = {"tiers": [{}]}
tier_updates = update_data["tiers"][0]
if name is not None:
tier_updates["name"] = name
if description is not None:
tier_updates["description"] = description
if monthly_price is not None:
tier_updates["monthly_price"] = monthly_price
if yearly_price is not None:
tier_updates["yearly_price"] = yearly_price
if benefits is not None:
tier_updates["benefits"] = benefits
if welcome_page_url is not None:
tier_updates["welcome_page_url"] = welcome_page_url
if visibility is not None:
tier_updates["visibility"] = visibility
if currency is not None:
tier_updates["currency"] = currency
if active is not None:
tier_updates["active"] = active
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 update tier {tier_id}")
response = await make_ghost_request(
f"tiers/{tier_id}/",
headers,
ctx,
http_method="PUT",
json_data=update_data
)
if ctx:
ctx.debug("Processing updated tier response")
tier = response.get("tiers", [{}])[0]
# Format response
benefits_text = "\n- ".join(tier.get('benefits', [])) if tier.get('benefits') else "None"
return f"""
Tier updated successfully:
Name: {tier.get('name')}
Type: {tier.get('type')}
Description: {tier.get('description', 'No description')}
Active: {tier.get('active', False)}
Visibility: {tier.get('visibility', 'public')}
Monthly Price: {tier.get('monthly_price', 'N/A')} {tier.get('currency', 'usd').upper()}
Yearly Price: {tier.get('yearly_price', 'N/A')} {tier.get('currency', 'usd').upper()}
Currency: {tier.get('currency', 'usd').upper()}
Benefits:
- {benefits_text}
ID: {tier.get('id')}
"""
except Exception as e:
if ctx:
ctx.error(f"Failed to update tier: {str(e)}")
raise