mirror of
https://github.com/jlengrand/ghost-mcp.git
synced 2026-03-10 08:21:19 +00:00
✨ Support offer creating and updating
This commit is contained in:
@@ -96,6 +96,8 @@ GHOST_API_URL=your_ghost_api_url GHOST_STAFF_API_KEY=your_staff_api_key npx @mod
|
||||
### Offers Management
|
||||
- `list_offers`: List promotional offers with relevant details
|
||||
- `read_offer`: Get detailed information on a specific offer
|
||||
- `create_offer`: Create a new promotional offer with specified details
|
||||
- `update_offer`: Update an existing offer with new information
|
||||
|
||||
### Newsletters Management
|
||||
- `list_newsletters`: List all newsletters associated with the blog
|
||||
|
||||
@@ -57,6 +57,8 @@ def create_server() -> FastMCP:
|
||||
mcp.tool()(tools.update_tier)
|
||||
mcp.tool()(tools.list_offers)
|
||||
mcp.tool()(tools.read_offer)
|
||||
mcp.tool()(tools.create_offer)
|
||||
mcp.tool()(tools.update_offer)
|
||||
mcp.tool()(tools.list_newsletters)
|
||||
mcp.tool()(tools.read_newsletter)
|
||||
mcp.tool()(tools.create_newsletter)
|
||||
|
||||
@@ -25,7 +25,9 @@ from .tools.tiers import (
|
||||
)
|
||||
from .tools.offers import (
|
||||
list_offers,
|
||||
read_offer
|
||||
read_offer,
|
||||
create_offer,
|
||||
update_offer
|
||||
)
|
||||
from .tools.newsletters import (
|
||||
list_newsletters,
|
||||
@@ -51,6 +53,8 @@ __all__ = [
|
||||
'update_tier',
|
||||
'list_offers',
|
||||
'read_offer',
|
||||
'create_offer',
|
||||
'update_offer',
|
||||
'list_newsletters',
|
||||
'read_newsletter',
|
||||
'create_newsletter',
|
||||
|
||||
@@ -4,7 +4,7 @@ from .posts import search_posts_by_title, list_posts, read_post, create_post, up
|
||||
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 .offers import list_offers, read_offer, create_offer, update_offer
|
||||
from .newsletters import list_newsletters, read_newsletter, create_newsletter, update_newsletter
|
||||
|
||||
__all__ = [
|
||||
@@ -24,6 +24,8 @@ __all__ = [
|
||||
'update_tier',
|
||||
'list_offers',
|
||||
'read_offer',
|
||||
'create_offer',
|
||||
'update_offer',
|
||||
'list_newsletters',
|
||||
'read_newsletter',
|
||||
'create_newsletter',
|
||||
|
||||
@@ -63,7 +63,8 @@ Display Title: {offer.get('display_title', 'No display title')}
|
||||
Type: {offer.get('type', 'Unknown')}
|
||||
Amount: {offer.get('amount', 'Unknown')}
|
||||
Duration: {offer.get('duration', 'Unknown')}
|
||||
Status: {offer.get('status', 'Unknown')}
|
||||
Status: {offer.get('status', 'Unknown')} d
|
||||
Redemption Count: {offer.get('redemption_count', 0)}
|
||||
Tier: {offer.get('tier', {}).get('name', 'Unknown')}
|
||||
ID: {offer.get('id', 'Unknown')}
|
||||
"""
|
||||
@@ -75,6 +76,216 @@ ID: {offer.get('id', 'Unknown')}
|
||||
ctx.error(f"Failed to list offers: {str(e)}")
|
||||
return str(e)
|
||||
|
||||
async def update_offer(
|
||||
offer_id: str,
|
||||
name: str = None,
|
||||
code: str = None,
|
||||
display_title: str = None,
|
||||
display_description: str = None,
|
||||
ctx: Context = None
|
||||
) -> str:
|
||||
"""Update an existing offer in Ghost.
|
||||
|
||||
Args:
|
||||
offer_id: ID of the offer to update (required)
|
||||
name: New internal name for the offer (optional)
|
||||
code: New shortcode for the offer (optional)
|
||||
display_title: New name displayed in the offer window (optional)
|
||||
display_description: New text displayed in the offer window (optional)
|
||||
ctx: Optional context for logging
|
||||
|
||||
Returns:
|
||||
String representation of the updated offer
|
||||
|
||||
Raises:
|
||||
GhostError: If the Ghost API request fails
|
||||
ValueError: If no fields to update are provided
|
||||
"""
|
||||
# Check if at least one editable field is provided
|
||||
if not any([name, code, display_title, display_description]):
|
||||
raise ValueError("At least one of name, code, display_title, or display_description must be provided")
|
||||
|
||||
if ctx:
|
||||
ctx.info(f"Updating offer with ID: {offer_id}")
|
||||
|
||||
# Construct update data with only provided fields
|
||||
update_data = {"offers": [{}]}
|
||||
offer_updates = update_data["offers"][0]
|
||||
|
||||
if name is not None:
|
||||
offer_updates["name"] = name
|
||||
if code is not None:
|
||||
offer_updates["code"] = code
|
||||
if display_title is not None:
|
||||
offer_updates["display_title"] = display_title
|
||||
if display_description is not None:
|
||||
offer_updates["display_description"] = display_description
|
||||
|
||||
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 offer {offer_id}")
|
||||
response = await make_ghost_request(
|
||||
f"offers/{offer_id}/",
|
||||
headers,
|
||||
ctx,
|
||||
http_method="PUT",
|
||||
json_data=update_data
|
||||
)
|
||||
|
||||
if ctx:
|
||||
ctx.debug("Processing updated offer response")
|
||||
|
||||
offer = response.get("offers", [{}])[0]
|
||||
|
||||
return f"""
|
||||
Offer updated successfully:
|
||||
Name: {offer.get('name')}
|
||||
Code: {offer.get('code')}
|
||||
Display Title: {offer.get('display_title', 'No display title')}
|
||||
Display Description: {offer.get('display_description', 'No description')}
|
||||
Type: {offer.get('type')}
|
||||
Status: {offer.get('status', 'active')}
|
||||
Cadence: {offer.get('cadence')}
|
||||
Amount: {offer.get('amount')}
|
||||
Duration: {offer.get('duration')}
|
||||
Duration in Months: {offer.get('duration_in_months', 'N/A')}
|
||||
Currency: {offer.get('currency', 'N/A')}
|
||||
Tier: {offer.get('tier', {}).get('name', 'Unknown')}
|
||||
ID: {offer.get('id')}
|
||||
"""
|
||||
except Exception as e:
|
||||
if ctx:
|
||||
ctx.error(f"Failed to update offer: {str(e)}")
|
||||
raise
|
||||
|
||||
async def create_offer(
|
||||
name: str,
|
||||
code: str,
|
||||
type: str,
|
||||
cadence: str,
|
||||
amount: int,
|
||||
tier_id: str,
|
||||
duration: str,
|
||||
display_title: str = None,
|
||||
display_description: str = None,
|
||||
currency: str = None,
|
||||
duration_in_months: int = None,
|
||||
ctx: Context = None
|
||||
) -> str:
|
||||
"""Create a new offer in Ghost.
|
||||
|
||||
Args:
|
||||
name: Internal name for the offer (required)
|
||||
code: Shortcode for the offer (required)
|
||||
type: Either 'percent' or 'fixed' (required)
|
||||
cadence: Either 'month' or 'year' (required)
|
||||
amount: Discount amount - percentage or fixed value (required)
|
||||
tier_id: ID of the tier to apply offer to (required)
|
||||
duration: Either 'once', 'forever' or 'repeating' (required)
|
||||
display_title: Name displayed in the offer window (optional)
|
||||
display_description: Text displayed in the offer window (optional)
|
||||
currency: Required when type is 'fixed', must match tier's currency (optional)
|
||||
duration_in_months: Required when duration is 'repeating' (optional)
|
||||
ctx: Optional context for logging
|
||||
|
||||
Returns:
|
||||
String representation of the created offer
|
||||
|
||||
Raises:
|
||||
GhostError: If the Ghost API request fails
|
||||
ValueError: If required parameters are missing or invalid
|
||||
"""
|
||||
if not all([name, code, type, cadence, amount, tier_id, duration]):
|
||||
raise ValueError("Missing required parameters")
|
||||
|
||||
if type not in ['percent', 'fixed']:
|
||||
raise ValueError("Type must be either 'percent' or 'fixed'")
|
||||
|
||||
if cadence not in ['month', 'year']:
|
||||
raise ValueError("Cadence must be either 'month' or 'year'")
|
||||
|
||||
if duration not in ['once', 'forever', 'repeating']:
|
||||
raise ValueError("Duration must be one of: 'once', 'forever', 'repeating'")
|
||||
|
||||
if duration == 'repeating' and not duration_in_months:
|
||||
raise ValueError("duration_in_months is required when duration is 'repeating'")
|
||||
|
||||
if type == 'fixed' and not currency:
|
||||
raise ValueError("Currency is required when type is 'fixed'")
|
||||
|
||||
if ctx:
|
||||
ctx.info(f"Creating new offer: {name}")
|
||||
|
||||
# Construct offer data
|
||||
offer_data = {
|
||||
"offers": [{
|
||||
"name": name,
|
||||
"code": code,
|
||||
"type": type,
|
||||
"cadence": cadence,
|
||||
"amount": amount,
|
||||
"duration": duration,
|
||||
"tier": {
|
||||
"id": tier_id
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
# Add optional fields if provided
|
||||
if display_title:
|
||||
offer_data["offers"][0]["display_title"] = display_title
|
||||
if display_description:
|
||||
offer_data["offers"][0]["display_description"] = display_description
|
||||
if currency:
|
||||
offer_data["offers"][0]["currency"] = currency
|
||||
if duration_in_months:
|
||||
offer_data["offers"][0]["duration_in_months"] = duration_in_months
|
||||
|
||||
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 offer")
|
||||
response = await make_ghost_request(
|
||||
"offers/",
|
||||
headers,
|
||||
ctx,
|
||||
http_method="POST",
|
||||
json_data=offer_data
|
||||
)
|
||||
|
||||
if ctx:
|
||||
ctx.debug("Processing created offer response")
|
||||
|
||||
offer = response.get("offers", [{}])[0]
|
||||
|
||||
return f"""
|
||||
Offer created successfully:
|
||||
Name: {offer.get('name')}
|
||||
Code: {offer.get('code')}
|
||||
Display Title: {offer.get('display_title', 'No display title')}
|
||||
Display Description: {offer.get('display_description', 'No description')}
|
||||
Type: {offer.get('type')}
|
||||
Status: {offer.get('status', 'active')}
|
||||
Cadence: {offer.get('cadence')}
|
||||
Amount: {offer.get('amount')}
|
||||
Duration: {offer.get('duration')}
|
||||
Duration in Months: {offer.get('duration_in_months', 'N/A')}
|
||||
Currency: {offer.get('currency', 'N/A')}
|
||||
Tier: {offer.get('tier', {}).get('name', 'Unknown')}
|
||||
ID: {offer.get('id')}
|
||||
"""
|
||||
except Exception as e:
|
||||
if ctx:
|
||||
ctx.error(f"Failed to create offer: {str(e)}")
|
||||
raise
|
||||
|
||||
async def read_offer(offer_id: str, ctx: Context = None) -> str:
|
||||
"""Get the details of a specific offer.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user