mirror of
https://github.com/jlengrand/ghost-mcp.git
synced 2026-03-10 08:21:19 +00:00
♻️ Refactor __init__.py
This commit is contained in:
@@ -1,54 +1,76 @@
|
|||||||
"""Ghost MCP tools package.
|
from .invites import create_invite
|
||||||
|
from .members import list_members, update_member, read_member, create_member
|
||||||
|
from .newsletters import list_newsletters, read_newsletter, create_newsletter, update_newsletter
|
||||||
|
from .offers import list_offers, read_offer, create_offer, update_offer
|
||||||
|
from .posts import (
|
||||||
|
list_posts,
|
||||||
|
search_posts_by_title,
|
||||||
|
read_post,
|
||||||
|
create_post,
|
||||||
|
update_post,
|
||||||
|
delete_post,
|
||||||
|
batchly_update_posts,
|
||||||
|
)
|
||||||
|
from .roles import list_roles
|
||||||
|
from .tags import browse_tags, read_tag, create_tag, update_tag, delete_tag
|
||||||
|
from .tiers import list_tiers, read_tier, create_tier, update_tier
|
||||||
|
from .users import list_users, read_user, delete_user
|
||||||
|
from .webhooks import create_webhook, update_webhook, delete_webhook
|
||||||
|
|
||||||
This module dynamically imports all tools from Python files in this directory.
|
__all__ = [
|
||||||
It automatically discovers and imports all non-private functions and variables,
|
# Invites
|
||||||
making them available at the package level.
|
"create_invite",
|
||||||
|
|
||||||
When adding new tools:
|
|
||||||
1. Create new Python files in this directory
|
|
||||||
2. Define your tools as functions in these files
|
|
||||||
3. No need to modify this file - tools will be imported automatically
|
|
||||||
"""
|
|
||||||
|
|
||||||
from importlib import import_module
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Any
|
|
||||||
|
|
||||||
def _import_submodules() -> Dict[str, Any]:
|
|
||||||
"""Dynamically import all modules from the current package.
|
|
||||||
|
|
||||||
Returns:
|
# Members
|
||||||
Dict mapping module names to imported module objects
|
"list_members",
|
||||||
|
"read_member",
|
||||||
|
"create_member",
|
||||||
|
"update_member",
|
||||||
|
|
||||||
Raises:
|
# Newsletters
|
||||||
FileNotFoundError: If the directory doesn't exist
|
"list_newsletters",
|
||||||
"""
|
"read_newsletter",
|
||||||
current_dir = Path(__file__).parent
|
"create_newsletter",
|
||||||
|
"update_newsletter",
|
||||||
|
|
||||||
if not current_dir.exists():
|
# Offers
|
||||||
raise FileNotFoundError(f"Tools directory not found at: {current_dir}")
|
"list_offers",
|
||||||
|
"read_offer",
|
||||||
|
"create_offer",
|
||||||
|
"update_offer",
|
||||||
|
|
||||||
modules: Dict[str, Any] = {}
|
# Posts
|
||||||
for py_file in current_dir.glob('*.py'):
|
"list_posts",
|
||||||
if py_file.name.startswith('__'):
|
"search_posts_by_title",
|
||||||
continue
|
"read_post",
|
||||||
|
"create_post",
|
||||||
module_name = py_file.stem
|
"update_post",
|
||||||
|
"delete_post",
|
||||||
# Import the module
|
"batchly_update_posts",
|
||||||
module = import_module(f".{module_name}", package="ghost_mcp.tools")
|
|
||||||
modules[module_name] = module
|
|
||||||
|
|
||||||
# Get all non-private attributes
|
|
||||||
for attr_name in dir(module):
|
|
||||||
if not attr_name.startswith('_'):
|
|
||||||
# Add to the current module's namespace
|
|
||||||
globals()[attr_name] = getattr(module, attr_name)
|
|
||||||
|
|
||||||
return modules
|
# Roles
|
||||||
|
"list_roles",
|
||||||
# Run the dynamic imports
|
|
||||||
_import_submodules()
|
# Tags
|
||||||
|
"browse_tags",
|
||||||
# Create sorted __all__ from the imported attributes for consistent ordering
|
"read_tag",
|
||||||
__all__ = sorted(name for name in globals() if not name.startswith('_'))
|
"create_tag",
|
||||||
|
"update_tag",
|
||||||
|
"delete_tag",
|
||||||
|
|
||||||
|
# Tiers
|
||||||
|
"list_tiers",
|
||||||
|
"read_tier",
|
||||||
|
"create_tier",
|
||||||
|
"update_tier",
|
||||||
|
|
||||||
|
# Users
|
||||||
|
"list_users",
|
||||||
|
"read_user",
|
||||||
|
"delete_user",
|
||||||
|
|
||||||
|
# Webhooks
|
||||||
|
"create_webhook",
|
||||||
|
"update_webhook",
|
||||||
|
"delete_webhook",
|
||||||
|
]
|
||||||
|
|||||||
@@ -543,7 +543,7 @@ Updated At: {post.get('updated_at', 'Unknown')}
|
|||||||
ctx.error(f"Failed to update post: {str(e)}")
|
ctx.error(f"Failed to update post: {str(e)}")
|
||||||
return str(e)
|
return str(e)
|
||||||
|
|
||||||
async def batchly_update_post(filter_criteria: dict, update_data: dict, ctx: Context = None) -> str:
|
async def batchly_update_posts(filter_criteria: dict, update_data: dict, ctx: Context = None) -> str:
|
||||||
"""Update multiple blog posts that match the filter criteria.
|
"""Update multiple blog posts that match the filter criteria.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -136,149 +136,6 @@ Email: {user.get('email', 'Unknown')}
|
|||||||
ctx.error(f"Failed to delete user: {str(e)}")
|
ctx.error(f"Failed to delete user: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def update_user(
|
|
||||||
user_id: str,
|
|
||||||
name: str = None,
|
|
||||||
slug: str = None,
|
|
||||||
email: str = None,
|
|
||||||
profile_image: str = None,
|
|
||||||
cover_image: str = None,
|
|
||||||
bio: str = None,
|
|
||||||
website: str = None,
|
|
||||||
location: str = None,
|
|
||||||
facebook: str = None,
|
|
||||||
twitter: str = None,
|
|
||||||
meta_title: str = None,
|
|
||||||
meta_description: str = None,
|
|
||||||
accessibility: str = None,
|
|
||||||
comment_notifications: bool = None,
|
|
||||||
free_member_signup_notification: bool = None,
|
|
||||||
paid_subscription_started_notification: bool = None,
|
|
||||||
paid_subscription_canceled_notification: bool = None,
|
|
||||||
mention_notifications: bool = None,
|
|
||||||
milestone_notifications: bool = None,
|
|
||||||
ctx: Context = None
|
|
||||||
) -> str:
|
|
||||||
"""Update an existing user in Ghost.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user_id: ID of the user to update (required)
|
|
||||||
name: User's full name (optional)
|
|
||||||
slug: User's slug (optional)
|
|
||||||
email: User's email address (optional)
|
|
||||||
profile_image: URL for profile image (optional)
|
|
||||||
cover_image: URL for cover image (optional)
|
|
||||||
bio: User's bio (optional)
|
|
||||||
website: User's website URL (optional)
|
|
||||||
location: User's location (optional)
|
|
||||||
facebook: Facebook username (optional)
|
|
||||||
twitter: Twitter username (optional)
|
|
||||||
meta_title: Meta title for SEO (optional)
|
|
||||||
meta_description: Meta description for SEO (optional)
|
|
||||||
accessibility: Accessibility settings (optional)
|
|
||||||
comment_notifications: Enable comment notifications (optional)
|
|
||||||
free_member_signup_notification: Enable free member signup notifications (optional)
|
|
||||||
paid_subscription_started_notification: Enable paid subscription started notifications (optional)
|
|
||||||
paid_subscription_canceled_notification: Enable paid subscription canceled notifications (optional)
|
|
||||||
mention_notifications: Enable mention notifications (optional)
|
|
||||||
milestone_notifications: Enable milestone notifications (optional)
|
|
||||||
ctx: Optional context for logging
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
String representation of the updated user
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
GhostError: If the Ghost API request fails
|
|
||||||
ValueError: If no fields to update are provided
|
|
||||||
"""
|
|
||||||
# Check if at least one field to update is provided
|
|
||||||
update_fields = {
|
|
||||||
'name': name,
|
|
||||||
'slug': slug,
|
|
||||||
'email': email,
|
|
||||||
'profile_image': profile_image,
|
|
||||||
'cover_image': cover_image,
|
|
||||||
'bio': bio,
|
|
||||||
'website': website,
|
|
||||||
'location': location,
|
|
||||||
'facebook': facebook,
|
|
||||||
'twitter': twitter,
|
|
||||||
'meta_title': meta_title,
|
|
||||||
'meta_description': meta_description,
|
|
||||||
'accessibility': accessibility,
|
|
||||||
'comment_notifications': comment_notifications,
|
|
||||||
'free_member_signup_notification': free_member_signup_notification,
|
|
||||||
'paid_subscription_started_notification': paid_subscription_started_notification,
|
|
||||||
'paid_subscription_canceled_notification': paid_subscription_canceled_notification,
|
|
||||||
'mention_notifications': mention_notifications,
|
|
||||||
'milestone_notifications': milestone_notifications
|
|
||||||
}
|
|
||||||
|
|
||||||
if not any(v is not None for v in update_fields.values()):
|
|
||||||
raise ValueError("At least one field must be provided to update")
|
|
||||||
|
|
||||||
if ctx:
|
|
||||||
ctx.info(f"Updating user with ID: {user_id}")
|
|
||||||
|
|
||||||
# Construct update data with only provided fields
|
|
||||||
update_data = {"users": [{}]}
|
|
||||||
user_updates = update_data["users"][0]
|
|
||||||
|
|
||||||
for field, value in update_fields.items():
|
|
||||||
if value is not None:
|
|
||||||
user_updates[field] = value
|
|
||||||
|
|
||||||
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 user {user_id}")
|
|
||||||
response = await make_ghost_request(
|
|
||||||
f"users/{user_id}/",
|
|
||||||
headers,
|
|
||||||
ctx,
|
|
||||||
http_method="PUT",
|
|
||||||
json_data=update_data
|
|
||||||
)
|
|
||||||
|
|
||||||
if ctx:
|
|
||||||
ctx.debug("Processing updated user response")
|
|
||||||
|
|
||||||
user = response.get("users", [{}])[0]
|
|
||||||
roles = [role.get('name') for role in user.get('roles', [])]
|
|
||||||
|
|
||||||
return f"""
|
|
||||||
User updated successfully:
|
|
||||||
Name: {user.get('name', 'Unknown')}
|
|
||||||
Email: {user.get('email', 'Unknown')}
|
|
||||||
Slug: {user.get('slug', 'Unknown')}
|
|
||||||
Status: {user.get('status', 'Unknown')}
|
|
||||||
Roles: {', '.join(roles)}
|
|
||||||
Location: {user.get('location', 'Not specified')}
|
|
||||||
Website: {user.get('website', 'None')}
|
|
||||||
Bio: {user.get('bio', 'No bio')}
|
|
||||||
Profile Image: {user.get('profile_image', 'None')}
|
|
||||||
Cover Image: {user.get('cover_image', 'None')}
|
|
||||||
Facebook: {user.get('facebook', 'None')}
|
|
||||||
Twitter: {user.get('twitter', 'None')}
|
|
||||||
Created: {user.get('created_at', 'Unknown')}
|
|
||||||
Updated: {user.get('updated_at', 'Unknown')}
|
|
||||||
Last Seen: {user.get('last_seen', 'Never')}
|
|
||||||
Notifications:
|
|
||||||
- Comments: {user.get('comment_notifications', False)}
|
|
||||||
- Free Member Signup: {user.get('free_member_signup_notification', False)}
|
|
||||||
- Paid Subscription Started: {user.get('paid_subscription_started_notification', False)}
|
|
||||||
- Paid Subscription Canceled: {user.get('paid_subscription_canceled_notification', False)}
|
|
||||||
- Mentions: {user.get('mention_notifications', False)}
|
|
||||||
- Milestones: {user.get('milestone_notifications', False)}
|
|
||||||
"""
|
|
||||||
except Exception as e:
|
|
||||||
if ctx:
|
|
||||||
ctx.error(f"Failed to update user: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def read_user(user_id: str, ctx: Context = None) -> str:
|
async def read_user(user_id: str, ctx: Context = None) -> str:
|
||||||
"""Get the details of a specific user.
|
"""Get the details of a specific user.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user