Initial commit: MCP Hub Community Edition v3.0.0
Community edition generated from private repo via sync pipeline. Includes 9 plugins (WordPress, WooCommerce, WP Advanced, Gitea, n8n, Supabase, OpenPanel, Appwrite, Directus) with ~587 tools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
441
plugins/openpanel/handlers/profiles.py
Normal file
441
plugins/openpanel/handlers/profiles.py
Normal file
@@ -0,0 +1,441 @@
|
||||
"""Profiles Handler - OpenPanel user profile management (8 tools)"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.openpanel.client import OpenPanelClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator (8 tools)"""
|
||||
return [
|
||||
{
|
||||
"name": "list_profiles",
|
||||
"method_name": "list_profiles",
|
||||
"description": "List user profiles with optional filtering and pagination.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Number of profiles to return",
|
||||
"default": 50,
|
||||
"maximum": 100,
|
||||
},
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"description": "Offset for pagination",
|
||||
"default": 0,
|
||||
},
|
||||
"sort_by": {
|
||||
"type": "string",
|
||||
"enum": ["last_seen", "first_seen", "event_count"],
|
||||
"description": "Sort order",
|
||||
"default": "last_seen",
|
||||
},
|
||||
},
|
||||
"required": ["project_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_profile",
|
||||
"method_name": "get_profile",
|
||||
"description": "Get detailed information about a specific user profile.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"profile_id": {"type": "string", "description": "User profile ID"},
|
||||
},
|
||||
"required": ["project_id", "profile_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "search_profiles",
|
||||
"method_name": "search_profiles",
|
||||
"description": "Search user profiles by property value.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"property": {
|
||||
"type": "string",
|
||||
"description": "Property to search (e.g., 'email', 'name', 'plan')",
|
||||
},
|
||||
"value": {"type": "string", "description": "Value to search for"},
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"enum": ["is", "contains", "startsWith", "endsWith"],
|
||||
"description": "Search operator",
|
||||
"default": "contains",
|
||||
},
|
||||
"limit": {"type": "integer", "description": "Maximum results", "default": 50},
|
||||
},
|
||||
"required": ["project_id", "property", "value"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_profile_events",
|
||||
"method_name": "get_profile_events",
|
||||
"description": "Get events for a specific user profile.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"profile_id": {"type": "string", "description": "User profile ID"},
|
||||
"event": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Filter by event name",
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Number of events",
|
||||
"default": 50,
|
||||
"maximum": 200,
|
||||
},
|
||||
},
|
||||
"required": ["project_id", "profile_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_profile_sessions",
|
||||
"method_name": "get_profile_sessions",
|
||||
"description": "Get sessions for a specific user profile.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"profile_id": {"type": "string", "description": "User profile ID"},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Number of sessions",
|
||||
"default": 20,
|
||||
"maximum": 50,
|
||||
},
|
||||
},
|
||||
"required": ["project_id", "profile_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "delete_profile",
|
||||
"method_name": "delete_profile",
|
||||
"description": "Delete a user profile and all associated data (GDPR compliance).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"profile_id": {"type": "string", "description": "User profile ID to delete"},
|
||||
"confirm": {
|
||||
"type": "boolean",
|
||||
"description": "Confirm deletion (required)",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["project_id", "profile_id", "confirm"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "merge_profiles",
|
||||
"method_name": "merge_profiles",
|
||||
"description": "Merge two user profiles into one (for duplicate resolution).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"primary_profile_id": {
|
||||
"type": "string",
|
||||
"description": "Primary profile ID to keep",
|
||||
},
|
||||
"secondary_profile_id": {
|
||||
"type": "string",
|
||||
"description": "Secondary profile ID to merge and delete",
|
||||
},
|
||||
},
|
||||
"required": ["project_id", "primary_profile_id", "secondary_profile_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "export_profile_data",
|
||||
"method_name": "export_profile_data",
|
||||
"description": "Export all data for a user profile (GDPR data portability).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Project ID"},
|
||||
"profile_id": {"type": "string", "description": "User profile ID"},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"enum": ["json", "csv"],
|
||||
"description": "Export format",
|
||||
"default": "json",
|
||||
},
|
||||
},
|
||||
"required": ["project_id", "profile_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
# =====================
|
||||
# Profile Functions (8)
|
||||
# =====================
|
||||
|
||||
async def list_profiles(
|
||||
client: OpenPanelClient,
|
||||
project_id: str,
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
sort_by: str = "last_seen",
|
||||
) -> str:
|
||||
"""List user profiles"""
|
||||
try:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"project_id": project_id,
|
||||
"pagination": {"limit": limit, "offset": offset, "sort_by": sort_by},
|
||||
"note": "Profile listing requires dashboard tRPC API. Use OpenPanel dashboard to view profiles.",
|
||||
"message": "Profile list request processed",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def get_profile(client: OpenPanelClient, project_id: str, profile_id: str) -> str:
|
||||
"""Get profile details"""
|
||||
try:
|
||||
# Try to get events for this profile via export API
|
||||
try:
|
||||
result = await client.export_events(
|
||||
project_id=project_id, profile_id=profile_id, limit=1, includes=["profile"]
|
||||
)
|
||||
|
||||
if isinstance(result, dict) and result.get("data"):
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"project_id": project_id,
|
||||
"profile_id": profile_id,
|
||||
"has_events": True,
|
||||
"note": "Full profile details require dashboard tRPC API.",
|
||||
"message": "Profile found with associated events",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"project_id": project_id,
|
||||
"profile_id": profile_id,
|
||||
"note": "Profile details require dashboard tRPC API. Use OpenPanel dashboard for full profile view.",
|
||||
"message": "Profile request processed",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def search_profiles(
|
||||
client: OpenPanelClient,
|
||||
project_id: str,
|
||||
property: str,
|
||||
value: str,
|
||||
operator: str = "contains",
|
||||
limit: int = 50,
|
||||
) -> str:
|
||||
"""Search profiles by property"""
|
||||
try:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"project_id": project_id,
|
||||
"search": {
|
||||
"property": property,
|
||||
"value": value,
|
||||
"operator": operator,
|
||||
"limit": limit,
|
||||
},
|
||||
"note": "Profile search requires dashboard tRPC API. Use OpenPanel dashboard for profile search.",
|
||||
"message": "Profile search request processed",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def get_profile_events(
|
||||
client: OpenPanelClient,
|
||||
project_id: str,
|
||||
profile_id: str,
|
||||
event: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> str:
|
||||
"""Get events for a profile"""
|
||||
try:
|
||||
result = await client.export_events(
|
||||
project_id=project_id, profile_id=profile_id, event=event, limit=limit
|
||||
)
|
||||
|
||||
events_count = len(result.get("data", [])) if isinstance(result, dict) else 0
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"project_id": project_id,
|
||||
"profile_id": profile_id,
|
||||
"event_filter": event,
|
||||
"count": events_count,
|
||||
"data": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def get_profile_sessions(
|
||||
client: OpenPanelClient, project_id: str, profile_id: str, limit: int = 20
|
||||
) -> str:
|
||||
"""Get sessions for a profile"""
|
||||
try:
|
||||
# Get session events
|
||||
result = await client.export_events(
|
||||
project_id=project_id, profile_id=profile_id, event="session_start", limit=limit
|
||||
)
|
||||
|
||||
sessions = result.get("data", []) if isinstance(result, dict) else []
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"project_id": project_id,
|
||||
"profile_id": profile_id,
|
||||
"sessions_count": len(sessions),
|
||||
"sessions": sessions,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def delete_profile(
|
||||
client: OpenPanelClient, project_id: str, profile_id: str, confirm: bool = False
|
||||
) -> str:
|
||||
"""Delete a user profile (GDPR)"""
|
||||
try:
|
||||
if not confirm:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": "Deletion not confirmed. Set confirm=true to proceed.",
|
||||
"warning": "This will permanently delete all user data.",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"project_id": project_id,
|
||||
"profile_id": profile_id,
|
||||
"confirmed": confirm,
|
||||
"note": "Profile deletion requires dashboard tRPC API. Use OpenPanel dashboard for GDPR deletion.",
|
||||
"message": "Profile deletion request processed (GDPR)",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def merge_profiles(
|
||||
client: OpenPanelClient, project_id: str, primary_profile_id: str, secondary_profile_id: str
|
||||
) -> str:
|
||||
"""Merge two profiles"""
|
||||
try:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"project_id": project_id,
|
||||
"primary_profile_id": primary_profile_id,
|
||||
"secondary_profile_id": secondary_profile_id,
|
||||
"note": "Profile merging requires dashboard tRPC API. Use OpenPanel dashboard for profile management.",
|
||||
"message": "Profile merge request processed",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def export_profile_data(
|
||||
client: OpenPanelClient, project_id: str, profile_id: str, format: str = "json"
|
||||
) -> str:
|
||||
"""Export all profile data (GDPR)"""
|
||||
try:
|
||||
# Export events for this profile
|
||||
result = await client.export_events(
|
||||
project_id=project_id, profile_id=profile_id, limit=1000, includes=["profile", "meta"]
|
||||
)
|
||||
|
||||
events = result.get("data", []) if isinstance(result, dict) else []
|
||||
|
||||
export_data = {
|
||||
"profile_id": profile_id,
|
||||
"project_id": project_id,
|
||||
"events_count": len(events),
|
||||
"events": events,
|
||||
}
|
||||
|
||||
if format == "csv":
|
||||
# Build CSV
|
||||
csv_lines = ["timestamp,event_name,properties"]
|
||||
for event in events:
|
||||
csv_lines.append(
|
||||
f"{event.get('timestamp', '')},{event.get('name', '')},{json.dumps(event.get('properties', {}))}"
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"profile_id": profile_id,
|
||||
"format": "csv",
|
||||
"events_count": len(events),
|
||||
"csv": "\n".join(csv_lines),
|
||||
"message": "Profile data exported (GDPR compliance)",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"profile_id": profile_id,
|
||||
"format": "json",
|
||||
"events_count": len(events),
|
||||
"data": export_data,
|
||||
"message": "Profile data exported (GDPR compliance)",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
Reference in New Issue
Block a user