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:
airano
2026-02-17 08:34:44 +03:30
commit cf62e65c55
237 changed files with 87596 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
"""
Directus Plugin Handlers
Each handler module provides:
1. Tool specifications (get_tool_specifications)
2. Handler functions for each tool
Phase J.1: items (12) + collections (14) = 26 tools
Phase J.2: files (12) + users (10) = 22 tools
Phase J.3: access (12) + automation (12) = 24 tools
Phase J.4: content (10) + dashboards (8) + system (10) = 28 tools
Total: 100 tools
"""
from plugins.directus.handlers import (
access,
automation,
collections,
content,
dashboards,
files,
items,
system,
users,
)
__all__ = [
"items",
"collections",
"files",
"users",
"access",
"automation",
"content",
"dashboards",
"system",
]

View File

@@ -0,0 +1,466 @@
"""
Access Control Handler - Roles, Permissions, Policies
Phase J.3: 12 tools
- Roles: list, get, create, update, delete (5)
- Permissions: list, get, create, update, delete, get_my (6)
- Policies: list (1)
Note: Directus v10+ uses policies for permissions.
The 'policy' parameter is required in create_permission.
"""
import json
from typing import Any
from plugins.directus.client import DirectusClient
def _parse_json_param(value: Any, param_name: str = "parameter") -> Any:
"""Parse a parameter that may be a JSON string or already a native type."""
if value is None:
return None
if isinstance(value, (dict, list)):
return value
if isinstance(value, str):
stripped = value.strip()
if stripped.startswith("{") or stripped.startswith("["):
try:
return json.loads(stripped)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in '{param_name}': {e}")
return value
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (12 tools)"""
return [
# =====================
# ROLES (5)
# =====================
{
"name": "list_roles",
"method_name": "list_roles",
"description": "List all roles in Directus.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Filter object",
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields",
},
"limit": {
"type": "integer",
"description": "Maximum roles to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_role",
"method_name": "get_role",
"description": "Get role details by ID.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Role UUID"}},
"required": ["id"],
},
"scope": "read",
},
{
"name": "create_role",
"method_name": "create_role",
"description": "Create a new role.",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Role name"},
"icon": {
"type": "string",
"description": "Material icon name",
"default": "supervised_user_circle",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Role description",
},
"admin_access": {
"type": "boolean",
"description": "Full admin access",
"default": False,
},
"app_access": {
"type": "boolean",
"description": "Access to admin app",
"default": True,
},
},
"required": ["name"],
},
"scope": "admin",
},
{
"name": "update_role",
"method_name": "update_role",
"description": "Update role settings.",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "Role UUID"},
"data": {
"type": "object",
"description": "Fields to update (name, icon, description, admin_access, etc.)",
},
},
"required": ["id", "data"],
},
"scope": "admin",
},
{
"name": "delete_role",
"method_name": "delete_role",
"description": "Delete a role. Users with this role will have no role.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Role UUID to delete"}},
"required": ["id"],
},
"scope": "admin",
},
# =====================
# PERMISSIONS (6)
# =====================
{
"name": "list_permissions",
"method_name": "list_permissions",
"description": "List all permissions.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Filter object (e.g., {"role": {"_eq": "role-uuid"}})',
},
"limit": {
"type": "integer",
"description": "Maximum permissions to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_permission",
"method_name": "get_permission",
"description": "Get permission details by ID.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Permission ID"}},
"required": ["id"],
},
"scope": "read",
},
{
"name": "create_permission",
"method_name": "create_permission",
"description": "Create a new permission rule. NOTE: In Directus v10+, 'policy' is required.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"action": {
"type": "string",
"enum": ["create", "read", "update", "delete", "share"],
"description": "Permission action",
},
"policy": {
"type": "string",
"description": "Policy UUID (REQUIRED in Directus v10+). Use list_policies to get available policies.",
},
"role": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Role UUID (null for public) - deprecated in v10+, use policy instead",
},
"permissions": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Filter rules for this permission",
},
"validation": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Validation rules for create/update",
},
"presets": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Default values for create",
},
"fields": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Allowed fields (* for all)",
},
},
"required": ["collection", "action", "policy"],
},
"scope": "admin",
},
{
"name": "update_permission",
"method_name": "update_permission",
"description": "Update a permission rule.",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "Permission ID"},
"data": {"type": "object", "description": "Fields to update"},
},
"required": ["id", "data"],
},
"scope": "admin",
},
{
"name": "delete_permission",
"method_name": "delete_permission",
"description": "Delete a permission rule.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Permission ID to delete"}},
"required": ["id"],
},
"scope": "admin",
},
{
"name": "get_my_permissions",
"method_name": "get_my_permissions",
"description": "Get the current user's effective permissions.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
# =====================
# POLICIES (1)
# =====================
{
"name": "list_policies",
"method_name": "list_policies",
"description": "List all access policies.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Filter object",
},
"limit": {
"type": "integer",
"description": "Maximum policies to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_roles(
client: DirectusClient,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
) -> str:
"""List roles."""
try:
# Parse JSON string parameters
parsed_filter = _parse_json_param(filter, "filter")
parsed_sort = _parse_json_param(sort, "sort")
result = await client.list_roles(filter=parsed_filter, sort=parsed_sort, limit=limit)
roles = result.get("data", [])
return json.dumps(
{"success": True, "total": len(roles), "roles": roles}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_role(client: DirectusClient, id: str) -> str:
"""Get role by ID."""
try:
result = await client.get_role(id)
return json.dumps(
{"success": True, "role": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_role(
client: DirectusClient,
name: str,
icon: str = "supervised_user_circle",
description: str | None = None,
admin_access: bool = False,
app_access: bool = True,
) -> str:
"""Create a new role."""
try:
result = await client.create_role(
name=name,
icon=icon,
description=description,
admin_access=admin_access,
app_access=app_access,
)
return json.dumps(
{"success": True, "message": f"Role '{name}' created", "role": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_role(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
"""Update role."""
try:
# Parse JSON string parameter
parsed_data = _parse_json_param(data, "data")
result = await client.update_role(id, parsed_data)
return json.dumps(
{"success": True, "message": "Role updated", "role": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_role(client: DirectusClient, id: str) -> str:
"""Delete a role."""
try:
await client.delete_role(id)
return json.dumps({"success": True, "message": f"Role {id} deleted"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_permissions(
client: DirectusClient, filter: dict | None = None, limit: int = 100
) -> str:
"""List permissions."""
try:
# Parse JSON string parameter
parsed_filter = _parse_json_param(filter, "filter")
result = await client.list_permissions(filter=parsed_filter, limit=limit)
permissions = result.get("data", [])
return json.dumps(
{"success": True, "total": len(permissions), "permissions": permissions},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_permission(client: DirectusClient, id: str) -> str:
"""Get permission by ID."""
try:
result = await client.get_permission(id)
return json.dumps(
{"success": True, "permission": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_permission(
client: DirectusClient,
collection: str,
action: str,
policy: str,
role: str | None = None,
permissions: dict | None = None,
validation: dict | None = None,
presets: dict | None = None,
fields: list[str] | None = None,
) -> str:
"""Create a new permission. Requires 'policy' in Directus v10+."""
try:
# Parse JSON string parameters
parsed_permissions = _parse_json_param(permissions, "permissions")
parsed_validation = _parse_json_param(validation, "validation")
parsed_presets = _parse_json_param(presets, "presets")
parsed_fields = _parse_json_param(fields, "fields")
result = await client.create_permission(
collection=collection,
action=action,
policy=policy,
role=role,
permissions=parsed_permissions,
validation=parsed_validation,
presets=parsed_presets,
fields=parsed_fields,
)
return json.dumps(
{
"success": True,
"message": f"Permission created for {collection}.{action}",
"permission": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_permission(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
"""Update permission."""
try:
# Parse JSON string parameter
parsed_data = _parse_json_param(data, "data")
result = await client.update_permission(id, parsed_data)
return json.dumps(
{"success": True, "message": "Permission updated", "permission": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_permission(client: DirectusClient, id: str) -> str:
"""Delete a permission."""
try:
await client.delete_permission(id)
return json.dumps({"success": True, "message": f"Permission {id} deleted"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_my_permissions(client: DirectusClient) -> str:
"""Get current user's permissions."""
try:
result = await client.get_my_permissions()
return json.dumps(
{"success": True, "permissions": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_policies(
client: DirectusClient, filter: dict | None = None, limit: int = 100
) -> str:
"""List policies."""
try:
# Parse JSON string parameter
parsed_filter = _parse_json_param(filter, "filter")
result = await client.list_policies(filter=parsed_filter, limit=limit)
policies = result.get("data", [])
return json.dumps(
{"success": True, "total": len(policies), "policies": policies},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,573 @@
"""
Automation Handler - Flows, Operations, Webhooks
Phase J.3: 12 tools
- Flows: list, get, create, update, delete, trigger (6)
- Operations: list, create (2)
- Webhooks: list, create, update, delete (4)
"""
import json
from typing import Any
from plugins.directus.client import DirectusClient
def _parse_json_param(value: Any, param_name: str = "parameter") -> Any:
"""Parse a parameter that may be a JSON string or already a native type."""
if value is None:
return None
if isinstance(value, (dict, list)):
return value
if isinstance(value, str):
stripped = value.strip()
if stripped.startswith("{") or stripped.startswith("["):
try:
return json.loads(stripped)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in '{param_name}': {e}")
return value
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (12 tools)"""
return [
# =====================
# FLOWS (6)
# =====================
{
"name": "list_flows",
"method_name": "list_flows",
"description": "List all automation flows.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Filter object",
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields",
},
"limit": {
"type": "integer",
"description": "Maximum flows to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_flow",
"method_name": "get_flow",
"description": "Get flow details by ID.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Flow UUID"}},
"required": ["id"],
},
"scope": "read",
},
{
"name": "create_flow",
"method_name": "create_flow",
"description": "Create a new automation flow.",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Flow name"},
"trigger": {
"type": "string",
"enum": ["event", "schedule", "operation", "webhook", "manual"],
"description": "Trigger type",
},
"status": {
"type": "string",
"enum": ["active", "inactive"],
"default": "active",
"description": "Flow status",
},
"icon": {"type": "string", "description": "Material icon", "default": "bolt"},
"options": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Trigger-specific options",
},
"accountability": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"enum": ["all", "activity", None],
"description": "Accountability tracking",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Flow description",
},
},
"required": ["name", "trigger"],
},
"scope": "admin",
},
{
"name": "update_flow",
"method_name": "update_flow",
"description": "Update flow settings.",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "Flow UUID"},
"data": {"type": "object", "description": "Fields to update"},
},
"required": ["id", "data"],
},
"scope": "admin",
},
{
"name": "delete_flow",
"method_name": "delete_flow",
"description": "Delete a flow and its operations.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Flow UUID to delete"}},
"required": ["id"],
},
"scope": "admin",
},
{
"name": "trigger_flow",
"method_name": "trigger_flow",
"description": "Manually trigger a flow.",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "Flow UUID"},
"data": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Data to pass to the flow",
},
},
"required": ["id"],
},
"scope": "write",
},
# =====================
# OPERATIONS (2)
# =====================
{
"name": "list_operations",
"method_name": "list_operations",
"description": "List all operations in flows.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Filter object (e.g., {"flow": {"_eq": "flow-uuid"}})',
},
"limit": {
"type": "integer",
"description": "Maximum operations to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "create_operation",
"method_name": "create_operation",
"description": "Create a new operation in a flow.",
"schema": {
"type": "object",
"properties": {
"flow": {"type": "string", "description": "Flow UUID"},
"type": {
"type": "string",
"description": "Operation type (e.g., 'log', 'mail', 'request', 'item-create')",
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Operation name",
},
"key": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Operation key (for referencing in other operations)",
},
"options": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Operation-specific options",
},
"position_x": {
"type": "integer",
"description": "X position in flow editor",
"default": 0,
},
"position_y": {
"type": "integer",
"description": "Y position in flow editor",
"default": 0,
},
},
"required": ["flow", "type"],
},
"scope": "admin",
},
# =====================
# WEBHOOKS (4)
# =====================
{
"name": "list_webhooks",
"method_name": "list_webhooks",
"description": "[DEPRECATED] List all webhooks. Webhooks are deprecated in Directus 10+, use Flows instead.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Filter object",
},
"limit": {
"type": "integer",
"description": "Maximum webhooks to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "create_webhook",
"method_name": "create_webhook",
"description": "[DEPRECATED] Create a new webhook. Use Flows instead in Directus 10+. See create_flow and create_operation for the recommended alternative.",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Webhook name"},
"url": {"type": "string", "description": "Target URL"},
"actions": {
"type": "array",
"items": {"type": "string"},
"description": "Actions to trigger (create, update, delete)",
},
"collections": {
"type": "array",
"items": {"type": "string"},
"description": "Collections to watch",
},
"method": {
"type": "string",
"enum": ["GET", "POST"],
"default": "POST",
"description": "HTTP method",
},
"status": {
"type": "string",
"enum": ["active", "inactive"],
"default": "active",
"description": "Webhook status",
},
"headers": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Custom headers",
},
},
"required": ["name", "url", "actions", "collections"],
},
"scope": "admin",
},
{
"name": "update_webhook",
"method_name": "update_webhook",
"description": "[DEPRECATED] Update webhook settings. Use Flows instead in Directus 10+.",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "Webhook ID"},
"data": {"type": "object", "description": "Fields to update"},
},
"required": ["id", "data"],
},
"scope": "admin",
},
{
"name": "delete_webhook",
"method_name": "delete_webhook",
"description": "[DEPRECATED] Delete a webhook. Use Flows instead in Directus 10+.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Webhook ID to delete"}},
"required": ["id"],
},
"scope": "admin",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_flows(
client: DirectusClient,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
) -> str:
"""List flows."""
try:
# Parse JSON string parameters
parsed_filter = _parse_json_param(filter, "filter")
parsed_sort = _parse_json_param(sort, "sort")
result = await client.list_flows(filter=parsed_filter, sort=parsed_sort, limit=limit)
flows = result.get("data", [])
return json.dumps(
{"success": True, "total": len(flows), "flows": flows}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_flow(client: DirectusClient, id: str) -> str:
"""Get flow by ID."""
try:
result = await client.get_flow(id)
return json.dumps(
{"success": True, "flow": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_flow(
client: DirectusClient,
name: str,
trigger: str,
status: str = "active",
icon: str = "bolt",
options: dict | None = None,
accountability: str | None = None,
description: str | None = None,
) -> str:
"""Create a new flow."""
try:
# Parse JSON string parameter
parsed_options = _parse_json_param(options, "options")
result = await client.create_flow(
name=name,
trigger=trigger,
status=status,
icon=icon,
options=parsed_options,
accountability=accountability,
description=description,
)
return json.dumps(
{"success": True, "message": f"Flow '{name}' created", "flow": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_flow(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
"""Update flow."""
try:
# Parse JSON string parameter
parsed_data = _parse_json_param(data, "data")
result = await client.update_flow(id, parsed_data)
return json.dumps(
{"success": True, "message": "Flow updated", "flow": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_flow(client: DirectusClient, id: str) -> str:
"""Delete a flow."""
try:
await client.delete_flow(id)
return json.dumps({"success": True, "message": f"Flow {id} deleted"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def trigger_flow(client: DirectusClient, id: str, data: dict | None = None) -> str:
"""Trigger a flow manually."""
try:
# Parse JSON string parameter
parsed_data = _parse_json_param(data, "data")
result = await client.trigger_flow(id, parsed_data)
return json.dumps(
{
"success": True,
"message": f"Flow {id} triggered",
"result": result.get("data") if result else None,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_operations(
client: DirectusClient, filter: dict | None = None, limit: int = 100
) -> str:
"""List operations."""
try:
# Parse JSON string parameter
parsed_filter = _parse_json_param(filter, "filter")
result = await client.list_operations(filter=parsed_filter, limit=limit)
operations = result.get("data", [])
return json.dumps(
{"success": True, "total": len(operations), "operations": operations},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_operation(
client: DirectusClient,
flow: str,
type: str,
name: str | None = None,
key: str | None = None,
options: dict | None = None,
position_x: int = 0,
position_y: int = 0,
) -> str:
"""Create a new operation."""
try:
# Parse JSON string parameter
parsed_options = _parse_json_param(options, "options")
result = await client.create_operation(
flow=flow,
type=type,
name=name,
key=key,
options=parsed_options,
position_x=position_x,
position_y=position_y,
)
return json.dumps(
{
"success": True,
"message": f"Operation '{type}' created in flow",
"operation": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_webhooks(
client: DirectusClient, filter: dict | None = None, limit: int = 100
) -> str:
"""List webhooks. DEPRECATED: Use Flows instead in Directus 10+."""
try:
# Parse JSON string parameter
parsed_filter = _parse_json_param(filter, "filter")
result = await client.list_webhooks(filter=parsed_filter, limit=limit)
webhooks = result.get("data", [])
return json.dumps(
{
"success": True,
"total": len(webhooks),
"webhooks": webhooks,
"warning": "DEPRECATED: Webhooks are deprecated in Directus 10+. Use Flows instead.",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_webhook(
client: DirectusClient,
name: str,
url: str,
actions: list[str],
collections: list[str],
method: str = "POST",
status: str = "active",
headers: dict | None = None,
) -> str:
"""Create a new webhook. DEPRECATED: Use Flows instead in Directus 10+."""
try:
# Parse JSON string parameters
parsed_actions = _parse_json_param(actions, "actions")
parsed_collections = _parse_json_param(collections, "collections")
parsed_headers = _parse_json_param(headers, "headers")
result = await client.create_webhook(
name=name,
url=url,
actions=parsed_actions,
collections=parsed_collections,
method=method,
status=status,
headers=parsed_headers,
)
return json.dumps(
{
"success": True,
"message": f"Webhook '{name}' created",
"warning": "DEPRECATED: Webhooks are deprecated in Directus 10+. Use Flows instead for better reliability. See create_flow and create_operation tools.",
"webhook": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
error_msg = str(e)
return json.dumps(
{
"success": False,
"error": error_msg,
"hint": "Webhooks are deprecated in Directus 10+. Consider using Flows instead: 1) create_flow with trigger='event', 2) create_operation with type='request' for HTTP calls.",
},
indent=2,
)
async def update_webhook(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
"""Update webhook. DEPRECATED: Use Flows instead in Directus 10+."""
try:
# Parse JSON string parameter
parsed_data = _parse_json_param(data, "data")
result = await client.update_webhook(id, parsed_data)
return json.dumps(
{
"success": True,
"message": "Webhook updated",
"warning": "DEPRECATED: Webhooks are deprecated in Directus 10+. Use Flows instead.",
"webhook": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps(
{
"success": False,
"error": str(e),
"hint": "Webhooks are deprecated in Directus 10+. Consider migrating to Flows.",
},
indent=2,
)
async def delete_webhook(client: DirectusClient, id: str) -> str:
"""Delete a webhook. DEPRECATED: Use Flows instead in Directus 10+."""
try:
await client.delete_webhook(id)
return json.dumps(
{
"success": True,
"message": f"Webhook {id} deleted",
"note": "Consider using Flows instead of Webhooks in Directus 10+.",
},
indent=2,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,544 @@
"""
Collections & Fields Handler - Schema management
Phase J.1: 14 tools
- Collections: list, get, create, update, delete (5)
- Fields: list, get, create, update, delete (5)
- Relations: list, get, create, delete (4)
"""
import json
from typing import Any
from plugins.directus.client import DirectusClient
def _parse_json_param(value: Any, param_name: str = "parameter") -> Any:
"""
Parse a parameter that may be a JSON string or already a native type.
MCP tools may receive object/array parameters as JSON strings.
This function safely converts them to proper Python types.
Args:
value: The value to parse (may be string, dict, list, or None)
param_name: Name of parameter for error messages
Returns:
Parsed value (dict, list, or original value if not JSON)
"""
if value is None:
return None
# Already the correct type
if isinstance(value, (dict, list)):
return value
# Try to parse JSON string
if isinstance(value, str):
stripped = value.strip()
# Check if it looks like JSON (starts with { or [)
if stripped.startswith("{") or stripped.startswith("["):
try:
return json.loads(stripped)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in '{param_name}': {e}")
return value
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (14 tools)"""
return [
# =====================
# COLLECTIONS (5)
# =====================
{
"name": "list_collections",
"method_name": "list_collections",
"description": "List all collections (tables) in Directus.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "get_collection",
"method_name": "get_collection",
"description": "Get collection details including schema and meta information.",
"schema": {
"type": "object",
"properties": {"collection": {"type": "string", "description": "Collection name"}},
"required": ["collection"],
},
"scope": "read",
},
{
"name": "create_collection",
"method_name": "create_collection",
"description": "Create a new collection (table) in Directus.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name (table name)"},
"meta": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Collection meta (icon, note, hidden, singleton, etc.)",
},
"schema": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Schema options (name, comment)",
},
"fields": {
"anyOf": [{"type": "array", "items": {"type": "object"}}, {"type": "null"}],
"description": "Initial fields to create with collection",
},
},
"required": ["collection"],
},
"scope": "admin",
},
{
"name": "update_collection",
"method_name": "update_collection",
"description": "Update collection meta information.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"meta": {
"type": "object",
"description": "Meta fields to update (icon, note, hidden, etc.)",
},
},
"required": ["collection", "meta"],
},
"scope": "admin",
},
{
"name": "delete_collection",
"method_name": "delete_collection",
"description": "Delete a collection and all its data. This action is irreversible.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name to delete"}
},
"required": ["collection"],
},
"scope": "admin",
},
# =====================
# FIELDS (5)
# =====================
{
"name": "list_fields",
"method_name": "list_fields",
"description": "List all fields, optionally filtered by collection.",
"schema": {
"type": "object",
"properties": {
"collection": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Collection name (optional, lists all fields if not provided)",
}
},
"required": [],
},
"scope": "read",
},
{
"name": "get_field",
"method_name": "get_field",
"description": "Get field details including schema, meta, and type information.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"field": {"type": "string", "description": "Field name"},
},
"required": ["collection", "field"],
},
"scope": "read",
},
{
"name": "create_field",
"method_name": "create_field",
"description": "Create a new field in a collection.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"field": {"type": "string", "description": "Field name"},
"type": {
"type": "string",
"description": "Field type (string, integer, boolean, uuid, datetime, json, etc.)",
},
"meta": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Field meta (interface, display, options, etc.)",
},
"schema": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Schema options (is_nullable, default_value, etc.)",
},
},
"required": ["collection", "field", "type"],
},
"scope": "admin",
},
{
"name": "update_field",
"method_name": "update_field",
"description": "Update field configuration.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"field": {"type": "string", "description": "Field name"},
"meta": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Meta fields to update",
},
"schema": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Schema fields to update",
},
},
"required": ["collection", "field"],
},
"scope": "admin",
},
{
"name": "delete_field",
"method_name": "delete_field",
"description": "Delete a field from a collection. This removes the column and all data.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"field": {"type": "string", "description": "Field name to delete"},
},
"required": ["collection", "field"],
},
"scope": "admin",
},
# =====================
# RELATIONS (4)
# =====================
{
"name": "list_relations",
"method_name": "list_relations",
"description": "List all relations (foreign keys), optionally filtered by collection.",
"schema": {
"type": "object",
"properties": {
"collection": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Collection name (optional)",
}
},
"required": [],
},
"scope": "read",
},
{
"name": "get_relation",
"method_name": "get_relation",
"description": "Get relation details.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"field": {"type": "string", "description": "Field name"},
},
"required": ["collection", "field"],
},
"scope": "read",
},
{
"name": "create_relation",
"method_name": "create_relation",
"description": "Create a new relation (foreign key) between collections.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name (many side)"},
"field": {"type": "string", "description": "Field name for the relation"},
"related_collection": {
"type": "string",
"description": "Related collection name (one side)",
},
"meta": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Relation meta (one_field, junction_field, etc.)",
},
"schema": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Schema options (on_delete, etc.)",
},
},
"required": ["collection", "field", "related_collection"],
},
"scope": "admin",
},
{
"name": "delete_relation",
"method_name": "delete_relation",
"description": "Delete a relation.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"field": {"type": "string", "description": "Field name"},
},
"required": ["collection", "field"],
},
"scope": "admin",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_collections(client: DirectusClient) -> str:
"""List all collections."""
try:
result = await client.list_collections()
collections = result.get("data", [])
return json.dumps(
{"success": True, "total": len(collections), "collections": collections},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_collection(client: DirectusClient, collection: str) -> str:
"""Get collection details."""
try:
result = await client.get_collection(collection)
return json.dumps(
{"success": True, "collection": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_collection(
client: DirectusClient,
collection: str,
meta: dict | None = None,
schema: dict | None = None,
fields: list[dict] | None = None,
) -> str:
"""Create a new collection."""
try:
# Parse JSON string parameters
parsed_meta = _parse_json_param(meta, "meta")
parsed_schema = _parse_json_param(schema, "schema")
parsed_fields = _parse_json_param(fields, "fields")
# Auto-fill schema.name if schema is provided but missing name
# This ensures a real database table is created, not just a folder collection
if parsed_schema is not None:
if not isinstance(parsed_schema, dict):
parsed_schema = {}
if "name" not in parsed_schema:
parsed_schema["name"] = collection
result = await client.create_collection(
collection=collection, meta=parsed_meta, schema=parsed_schema, fields=parsed_fields
)
return json.dumps(
{
"success": True,
"message": f"Collection '{collection}' created",
"collection": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_collection(client: DirectusClient, collection: str, meta: dict) -> str:
"""Update collection meta."""
try:
# Parse JSON string parameter
parsed_meta = _parse_json_param(meta, "meta")
result = await client.update_collection(collection, parsed_meta)
return json.dumps(
{
"success": True,
"message": f"Collection '{collection}' updated",
"collection": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_collection(client: DirectusClient, collection: str) -> str:
"""Delete a collection."""
try:
await client.delete_collection(collection)
return json.dumps(
{"success": True, "message": f"Collection '{collection}' deleted"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_fields(client: DirectusClient, collection: str | None = None) -> str:
"""List fields."""
try:
result = await client.list_fields(collection)
fields = result.get("data", [])
return json.dumps(
{"success": True, "collection": collection, "total": len(fields), "fields": fields},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_field(client: DirectusClient, collection: str, field: str) -> str:
"""Get field details."""
try:
result = await client.get_field(collection, field)
return json.dumps(
{"success": True, "field": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_field(
client: DirectusClient,
collection: str,
field: str,
type: str,
meta: dict | None = None,
schema: dict | None = None,
) -> str:
"""Create a new field."""
try:
# Parse JSON string parameters
parsed_meta = _parse_json_param(meta, "meta")
parsed_schema = _parse_json_param(schema, "schema")
result = await client.create_field(
collection=collection, field=field, type=type, meta=parsed_meta, schema=parsed_schema
)
return json.dumps(
{
"success": True,
"message": f"Field '{field}' created in {collection}",
"field": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_field(
client: DirectusClient,
collection: str,
field: str,
meta: dict | None = None,
schema: dict | None = None,
) -> str:
"""Update field configuration."""
try:
# Parse JSON string parameters
parsed_meta = _parse_json_param(meta, "meta")
parsed_schema = _parse_json_param(schema, "schema")
result = await client.update_field(
collection=collection, field=field, meta=parsed_meta, schema=parsed_schema
)
return json.dumps(
{"success": True, "message": f"Field '{field}' updated", "field": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_field(client: DirectusClient, collection: str, field: str) -> str:
"""Delete a field."""
try:
await client.delete_field(collection, field)
return json.dumps(
{"success": True, "message": f"Field '{field}' deleted from {collection}"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_relations(client: DirectusClient, collection: str | None = None) -> str:
"""List relations."""
try:
result = await client.list_relations(collection)
relations = result.get("data", [])
return json.dumps(
{
"success": True,
"collection": collection,
"total": len(relations),
"relations": relations,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_relation(client: DirectusClient, collection: str, field: str) -> str:
"""Get relation details."""
try:
result = await client.get_relation(collection, field)
return json.dumps(
{"success": True, "relation": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_relation(
client: DirectusClient,
collection: str,
field: str,
related_collection: str,
meta: dict | None = None,
schema: dict | None = None,
) -> str:
"""Create a new relation."""
try:
# Parse JSON string parameters
parsed_meta = _parse_json_param(meta, "meta")
parsed_schema = _parse_json_param(schema, "schema")
result = await client.create_relation(
collection=collection,
field=field,
related_collection=related_collection,
meta=parsed_meta,
schema=parsed_schema,
)
return json.dumps(
{
"success": True,
"message": f"Relation created: {collection}.{field} -> {related_collection}",
"relation": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_relation(client: DirectusClient, collection: str, field: str) -> str:
"""Delete a relation."""
try:
await client.delete_relation(collection, field)
return json.dumps(
{"success": True, "message": f"Relation {collection}.{field} deleted"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,370 @@
"""
Content Handler - Revisions, Versions, Comments
Phase J.4: 10 tools
- Revisions: list, get (2)
- Versions: list, get, create, update, delete, promote (6)
- Comments: list, create (2)
"""
import json
from typing import Any
from plugins.directus.client import DirectusClient
def _parse_json_param(value: Any, param_name: str = "parameter") -> Any:
"""Parse a parameter that may be a JSON string or already a native type."""
if value is None:
return None
if isinstance(value, (dict, list)):
return value
if isinstance(value, str):
stripped = value.strip()
if stripped.startswith("{") or stripped.startswith("["):
try:
return json.loads(stripped)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in '{param_name}': {e}")
return value
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (10 tools)"""
return [
# =====================
# REVISIONS (2)
# =====================
{
"name": "list_revisions",
"method_name": "list_revisions",
"description": "List revisions (history) of items.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Filter object (e.g., {"collection": {"_eq": "posts"}})',
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields (default: ['-activity'])",
},
"limit": {
"type": "integer",
"description": "Maximum revisions to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_revision",
"method_name": "get_revision",
"description": "Get revision details by ID.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Revision ID"}},
"required": ["id"],
},
"scope": "read",
},
# =====================
# VERSIONS (6)
# =====================
{
"name": "list_versions",
"method_name": "list_versions",
"description": "List content versions (drafts).",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Filter object",
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields",
},
"limit": {
"type": "integer",
"description": "Maximum versions to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_version",
"method_name": "get_version",
"description": "Get version details by ID.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Version UUID"}},
"required": ["id"],
},
"scope": "read",
},
{
"name": "create_version",
"method_name": "create_version",
"description": "Create a new content version (draft).",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Version name"},
"collection": {"type": "string", "description": "Collection name"},
"item": {"type": "string", "description": "Item ID"},
"key": {"type": "string", "description": "Version key (unique identifier)"},
},
"required": ["name", "collection", "item", "key"],
},
"scope": "write",
},
{
"name": "update_version",
"method_name": "update_version",
"description": "Update a version.",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "Version UUID"},
"data": {"type": "object", "description": "Fields to update (name, delta)"},
},
"required": ["id", "data"],
},
"scope": "write",
},
{
"name": "delete_version",
"method_name": "delete_version",
"description": "Delete a version.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Version UUID to delete"}},
"required": ["id"],
},
"scope": "write",
},
{
"name": "promote_version",
"method_name": "promote_version",
"description": "Promote a version to main (apply changes to item).",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "Version UUID to promote"},
"mainHash": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Expected main content hash (for conflict detection)",
},
},
"required": ["id"],
},
"scope": "write",
},
# =====================
# COMMENTS (2)
# =====================
{
"name": "list_comments",
"method_name": "list_comments",
"description": "List comments on items.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Filter object (e.g., {"collection": {"_eq": "posts"}})',
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields",
},
"limit": {
"type": "integer",
"description": "Maximum comments to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "create_comment",
"method_name": "create_comment",
"description": "Create a comment on an item.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"item": {"type": "string", "description": "Item ID"},
"comment": {"type": "string", "description": "Comment text"},
},
"required": ["collection", "item", "comment"],
},
"scope": "write",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_revisions(
client: DirectusClient,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
) -> str:
"""List revisions."""
try:
# Parse JSON string parameters
parsed_filter = _parse_json_param(filter, "filter")
parsed_sort = _parse_json_param(sort, "sort")
result = await client.list_revisions(filter=parsed_filter, sort=parsed_sort, limit=limit)
revisions = result.get("data", [])
return json.dumps(
{"success": True, "total": len(revisions), "revisions": revisions},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_revision(client: DirectusClient, id: str) -> str:
"""Get revision by ID."""
try:
result = await client.get_revision(id)
return json.dumps(
{"success": True, "revision": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_versions(
client: DirectusClient,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
) -> str:
"""List versions."""
try:
# Parse JSON string parameters
parsed_filter = _parse_json_param(filter, "filter")
parsed_sort = _parse_json_param(sort, "sort")
result = await client.list_versions(filter=parsed_filter, sort=parsed_sort, limit=limit)
versions = result.get("data", [])
return json.dumps(
{"success": True, "total": len(versions), "versions": versions},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_version(client: DirectusClient, id: str) -> str:
"""Get version by ID."""
try:
result = await client.get_version(id)
return json.dumps(
{"success": True, "version": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_version(
client: DirectusClient, name: str, collection: str, item: str, key: str
) -> str:
"""Create a new version."""
try:
result = await client.create_version(name=name, collection=collection, item=item, key=key)
return json.dumps(
{
"success": True,
"message": f"Version '{name}' created",
"version": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_version(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
"""Update version."""
try:
# Parse JSON string parameter
parsed_data = _parse_json_param(data, "data")
result = await client.update_version(id, parsed_data)
return json.dumps(
{"success": True, "message": "Version updated", "version": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_version(client: DirectusClient, id: str) -> str:
"""Delete a version."""
try:
await client.delete_version(id)
return json.dumps({"success": True, "message": f"Version {id} deleted"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def promote_version(client: DirectusClient, id: str, mainHash: str | None = None) -> str:
"""Promote version to main."""
try:
result = await client.promote_version(id, mainHash)
return json.dumps(
{
"success": True,
"message": f"Version {id} promoted to main",
"result": result.get("data") if result else None,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_comments(
client: DirectusClient,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
) -> str:
"""List comments."""
try:
# Parse JSON string parameters
parsed_filter = _parse_json_param(filter, "filter")
parsed_sort = _parse_json_param(sort, "sort")
result = await client.list_comments(filter=parsed_filter, sort=parsed_sort, limit=limit)
comments = result.get("data", [])
return json.dumps(
{"success": True, "total": len(comments), "comments": comments},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_comment(client: DirectusClient, collection: str, item: str, comment: str) -> str:
"""Create a comment."""
try:
result = await client.create_comment(collection, item, comment)
return json.dumps(
{"success": True, "message": "Comment created", "comment": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,332 @@
"""
Dashboards Handler - Dashboards & Panels
Phase J.4: 8 tools
- Dashboards: list, get, create, update, delete (5)
- Panels: list, create, delete (3)
"""
import json
from typing import Any
from plugins.directus.client import DirectusClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (8 tools)"""
return [
# =====================
# DASHBOARDS (5)
# =====================
{
"name": "list_dashboards",
"method_name": "list_dashboards",
"description": "List all insights dashboards.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Filter object",
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields",
},
"limit": {
"type": "integer",
"description": "Maximum dashboards to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_dashboard",
"method_name": "get_dashboard",
"description": "Get dashboard details by ID.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Dashboard UUID"}},
"required": ["id"],
},
"scope": "read",
},
{
"name": "create_dashboard",
"method_name": "create_dashboard",
"description": "Create a new insights dashboard.",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Dashboard name"},
"icon": {
"type": "string",
"description": "Material icon",
"default": "dashboard",
},
"note": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Dashboard description",
},
"color": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Accent color",
},
},
"required": ["name"],
},
"scope": "write",
},
{
"name": "update_dashboard",
"method_name": "update_dashboard",
"description": "Update dashboard settings.",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "Dashboard UUID"},
"data": {
"type": "object",
"description": "Fields to update (name, icon, note, color)",
},
},
"required": ["id", "data"],
},
"scope": "write",
},
{
"name": "delete_dashboard",
"method_name": "delete_dashboard",
"description": "Delete a dashboard and its panels.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Dashboard UUID to delete"}},
"required": ["id"],
},
"scope": "write",
},
# =====================
# PANELS (3)
# =====================
{
"name": "list_panels",
"method_name": "list_panels",
"description": "List all panels, optionally filtered by dashboard.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Filter object (e.g., {"dashboard": {"_eq": "dashboard-uuid"}})',
},
"limit": {
"type": "integer",
"description": "Maximum panels to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "create_panel",
"method_name": "create_panel",
"description": "Create a new panel in a dashboard.",
"schema": {
"type": "object",
"properties": {
"dashboard": {"type": "string", "description": "Dashboard UUID"},
"type": {
"type": "string",
"description": "Panel type (label, list, metric, time-series, etc.)",
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Panel name",
},
"icon": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Material icon",
},
"color": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Accent color",
},
"note": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Panel description",
},
"width": {
"type": "integer",
"description": "Panel width (1-12 grid units)",
"default": 12,
},
"height": {
"type": "integer",
"description": "Panel height (grid units)",
"default": 6,
},
"position_x": {
"type": "integer",
"description": "X position in dashboard",
"default": 0,
},
"position_y": {
"type": "integer",
"description": "Y position in dashboard",
"default": 0,
},
"options": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Panel-specific options",
},
},
"required": ["dashboard", "type"],
},
"scope": "write",
},
{
"name": "delete_panel",
"method_name": "delete_panel",
"description": "Delete a panel from a dashboard.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Panel UUID to delete"}},
"required": ["id"],
},
"scope": "write",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_dashboards(
client: DirectusClient,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
) -> str:
"""List dashboards."""
try:
result = await client.list_dashboards(filter=filter, sort=sort, limit=limit)
dashboards = result.get("data", [])
return json.dumps(
{"success": True, "total": len(dashboards), "dashboards": dashboards},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_dashboard(client: DirectusClient, id: str) -> str:
"""Get dashboard by ID."""
try:
result = await client.get_dashboard(id)
return json.dumps(
{"success": True, "dashboard": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_dashboard(
client: DirectusClient,
name: str,
icon: str = "dashboard",
note: str | None = None,
color: str | None = None,
) -> str:
"""Create a new dashboard."""
try:
result = await client.create_dashboard(name=name, icon=icon, note=note, color=color)
return json.dumps(
{
"success": True,
"message": f"Dashboard '{name}' created",
"dashboard": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_dashboard(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
"""Update dashboard."""
try:
result = await client.update_dashboard(id, data)
return json.dumps(
{"success": True, "message": "Dashboard updated", "dashboard": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_dashboard(client: DirectusClient, id: str) -> str:
"""Delete a dashboard."""
try:
await client.delete_dashboard(id)
return json.dumps({"success": True, "message": f"Dashboard {id} deleted"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_panels(client: DirectusClient, filter: dict | None = None, limit: int = 100) -> str:
"""List panels."""
try:
result = await client.list_panels(filter=filter, limit=limit)
panels = result.get("data", [])
return json.dumps(
{"success": True, "total": len(panels), "panels": panels}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_panel(
client: DirectusClient,
dashboard: str,
type: str,
name: str | None = None,
icon: str | None = None,
color: str | None = None,
note: str | None = None,
width: int = 12,
height: int = 6,
position_x: int = 0,
position_y: int = 0,
options: dict | None = None,
) -> str:
"""Create a new panel."""
try:
result = await client.create_panel(
dashboard=dashboard,
type=type,
name=name,
icon=icon,
color=color,
note=note,
width=width,
height=height,
position_x=position_x,
position_y=position_y,
options=options,
)
return json.dumps(
{"success": True, "message": f"Panel '{type}' created", "panel": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_panel(client: DirectusClient, id: str) -> str:
"""Delete a panel."""
try:
await client.delete_panel(id)
return json.dumps({"success": True, "message": f"Panel {id} deleted"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,406 @@
"""
Files & Folders Handler - Asset management
Phase J.2: 12 tools
- Files: list, get, update, delete, delete_files, import_url (6)
- Folders: list, get, create, update, delete (5)
- Note: File upload requires multipart form - import_url is the alternative
"""
import json
from typing import Any
from plugins.directus.client import DirectusClient
def _parse_json_param(value: Any, param_name: str = "parameter") -> Any:
"""Parse a parameter that may be a JSON string or already a native type."""
if value is None:
return None
if isinstance(value, (dict, list)):
return value
if isinstance(value, str):
stripped = value.strip()
if stripped.startswith("{") or stripped.startswith("["):
try:
return json.loads(stripped)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in '{param_name}': {e}")
return value
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (12 tools)"""
return [
# =====================
# FILES (7)
# =====================
{
"name": "list_files",
"method_name": "list_files",
"description": "List all files in Directus storage with filtering options.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Filter object (e.g., {"type": {"_contains": "image"}})',
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields (e.g., ['-uploaded_on'])",
},
"limit": {
"type": "integer",
"description": "Maximum files to return",
"default": 100,
},
"offset": {"type": "integer", "description": "Files to skip", "default": 0},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_file",
"method_name": "get_file",
"description": "Get file metadata by ID.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "File UUID"}},
"required": ["id"],
},
"scope": "read",
},
{
"name": "update_file",
"method_name": "update_file",
"description": "Update file metadata (title, description, tags, folder).",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "File UUID"},
"data": {
"type": "object",
"description": "Fields to update (title, description, tags, folder, etc.)",
},
},
"required": ["id", "data"],
},
"scope": "write",
},
{
"name": "delete_file",
"method_name": "delete_file",
"description": "Delete a file. This action is irreversible.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "File UUID to delete"}},
"required": ["id"],
},
"scope": "admin",
},
{
"name": "delete_files",
"method_name": "delete_files",
"description": "Delete multiple files. This action is irreversible.",
"schema": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"items": {"type": "string"},
"description": "Array of file UUIDs to delete",
}
},
"required": ["ids"],
},
"scope": "admin",
},
{
"name": "import_file_url",
"method_name": "import_file_url",
"description": "Import a file from a URL into Directus storage.",
"schema": {
"type": "object",
"properties": {
"url": {"type": "string", "description": "URL of the file to import"},
"data": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Additional file data (title, description, folder, etc.)",
},
},
"required": ["url"],
},
"scope": "write",
},
{
"name": "get_file_url",
"method_name": "get_file_url",
"description": "Get the direct URL to access a file.",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "File UUID"},
"download": {
"type": "boolean",
"description": "Whether to force download instead of display",
"default": False,
},
},
"required": ["id"],
},
"scope": "read",
},
# =====================
# FOLDERS (5)
# =====================
{
"name": "list_folders",
"method_name": "list_folders",
"description": "List all folders in Directus storage.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Filter object",
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields",
},
"limit": {
"type": "integer",
"description": "Maximum folders to return",
"default": 100,
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_folder",
"method_name": "get_folder",
"description": "Get folder details by ID.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Folder UUID"}},
"required": ["id"],
},
"scope": "read",
},
{
"name": "create_folder",
"method_name": "create_folder",
"description": "Create a new folder.",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Folder name"},
"parent": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Parent folder UUID (null for root)",
},
},
"required": ["name"],
},
"scope": "write",
},
{
"name": "update_folder",
"method_name": "update_folder",
"description": "Update folder name or parent.",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "Folder UUID"},
"data": {"type": "object", "description": "Fields to update (name, parent)"},
},
"required": ["id", "data"],
},
"scope": "write",
},
{
"name": "delete_folder",
"method_name": "delete_folder",
"description": "Delete a folder. This action is irreversible.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "Folder UUID to delete"}},
"required": ["id"],
},
"scope": "admin",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_files(
client: DirectusClient,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
offset: int = 0,
search: str | None = None,
) -> str:
"""List files."""
try:
# Parse JSON string parameters
parsed_filter = _parse_json_param(filter, "filter")
parsed_sort = _parse_json_param(sort, "sort")
result = await client.list_files(
filter=parsed_filter, sort=parsed_sort, limit=limit, offset=offset, search=search
)
files = result.get("data", [])
return json.dumps(
{"success": True, "total": len(files), "files": files}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_file(client: DirectusClient, id: str) -> str:
"""Get file metadata."""
try:
result = await client.get_file(id)
return json.dumps(
{"success": True, "file": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_file(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
"""Update file metadata."""
try:
# Parse JSON string parameter
parsed_data = _parse_json_param(data, "data")
result = await client.update_file(id, parsed_data)
return json.dumps(
{"success": True, "message": "File updated", "file": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_file(client: DirectusClient, id: str) -> str:
"""Delete a file."""
try:
await client.delete_file(id)
return json.dumps({"success": True, "message": f"File {id} deleted"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_files(client: DirectusClient, ids: list[str]) -> str:
"""Delete multiple files."""
try:
# Parse JSON string parameter
parsed_ids = _parse_json_param(ids, "ids")
await client.delete_files(parsed_ids)
return json.dumps({"success": True, "message": f"Deleted {len(ids)} files"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def import_file_url(client: DirectusClient, url: str, data: dict | None = None) -> str:
"""Import file from URL."""
try:
# Parse JSON string parameter
parsed_data = _parse_json_param(data, "data")
result = await client.import_file_url(url, parsed_data)
return json.dumps(
{"success": True, "message": "File imported", "file": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_file_url(client: DirectusClient, id: str, download: bool = False) -> str:
"""Get file URL."""
try:
base_url = client.base_url
url = f"{base_url}/assets/{id}"
if download:
url += "?download=true"
return json.dumps({"success": True, "id": id, "url": url, "download": download}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_folders(
client: DirectusClient,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
search: str | None = None,
) -> str:
"""List folders."""
try:
# Parse JSON string parameters
parsed_filter = _parse_json_param(filter, "filter")
parsed_sort = _parse_json_param(sort, "sort")
result = await client.list_folders(
filter=parsed_filter, sort=parsed_sort, limit=limit, search=search
)
folders = result.get("data", [])
return json.dumps(
{"success": True, "total": len(folders), "folders": folders},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_folder(client: DirectusClient, id: str) -> str:
"""Get folder details."""
try:
result = await client.get_folder(id)
return json.dumps(
{"success": True, "folder": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_folder(client: DirectusClient, name: str, parent: str | None = None) -> str:
"""Create a folder."""
try:
result = await client.create_folder(name, parent)
return json.dumps(
{"success": True, "message": f"Folder '{name}' created", "folder": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_folder(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
"""Update folder."""
try:
# Parse JSON string parameter
parsed_data = _parse_json_param(data, "data")
result = await client.update_folder(id, parsed_data)
return json.dumps(
{"success": True, "message": "Folder updated", "folder": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_folder(client: DirectusClient, id: str) -> str:
"""Delete a folder."""
try:
await client.delete_folder(id)
return json.dumps({"success": True, "message": f"Folder {id} deleted"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,576 @@
"""
Items Handler - CRUD operations for any collection
Phase J.1: 12 tools
- list_items, get_item, create_item, create_items
- update_item, update_items, delete_item, delete_items
- search_items, aggregate_items, export_items, import_items
"""
import json
from typing import Any
from plugins.directus.client import DirectusClient
def _parse_json_param(value: Any, param_name: str = "parameter") -> Any:
"""Parse a parameter that may be a JSON string or already a native type."""
if value is None:
return None
if isinstance(value, (dict, list)):
return value
if isinstance(value, str):
stripped = value.strip()
if stripped.startswith("{") or stripped.startswith("["):
try:
return json.loads(stripped)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in '{param_name}': {e}")
return value
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (12 tools)"""
return [
{
"name": "list_items",
"method_name": "list_items",
"description": "List items from any Directus collection with filtering, sorting, and pagination.",
"schema": {
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": "Collection name (e.g., 'posts', 'products')",
},
"fields": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Fields to return (e.g., ['id', 'title', 'author.*'])",
},
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Filter object (e.g., {"status": {"_eq": "published"}})',
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields (e.g., ['-date_created', 'title'])",
},
"limit": {
"type": "integer",
"description": "Maximum items to return",
"default": 100,
},
"offset": {"type": "integer", "description": "Items to skip", "default": 0},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Full-text search query",
},
},
"required": ["collection"],
},
"scope": "read",
},
{
"name": "get_item",
"method_name": "get_item",
"description": "Get a single item by ID from any collection.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"id": {"type": "string", "description": "Item ID"},
"fields": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Fields to return",
},
},
"required": ["collection", "id"],
},
"scope": "read",
},
{
"name": "create_item",
"method_name": "create_item",
"description": "Create a new item in any collection.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"data": {"type": "object", "description": "Item data (field values)"},
"fields": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Fields to return in response",
},
},
"required": ["collection", "data"],
},
"scope": "write",
},
{
"name": "create_items",
"method_name": "create_items",
"description": "Create multiple items in a collection at once.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"data": {
"type": "array",
"items": {"type": "object"},
"description": "Array of item data objects",
},
"fields": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Fields to return in response",
},
},
"required": ["collection", "data"],
},
"scope": "write",
},
{
"name": "update_item",
"method_name": "update_item",
"description": "Update an existing item by ID.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"id": {"type": "string", "description": "Item ID"},
"data": {"type": "object", "description": "Fields to update"},
"fields": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Fields to return in response",
},
},
"required": ["collection", "id", "data"],
},
"scope": "write",
},
{
"name": "update_items",
"method_name": "update_items",
"description": "Update multiple items by their IDs.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"keys": {
"type": "array",
"items": {"type": "string"},
"description": "Array of item IDs to update",
},
"data": {
"type": "object",
"description": "Fields to update (applied to all items)",
},
"fields": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Fields to return in response",
},
},
"required": ["collection", "keys", "data"],
},
"scope": "write",
},
{
"name": "delete_item",
"method_name": "delete_item",
"description": "Delete an item by ID. This action is irreversible.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"id": {"type": "string", "description": "Item ID to delete"},
},
"required": ["collection", "id"],
},
"scope": "admin",
},
{
"name": "delete_items",
"method_name": "delete_items",
"description": "Delete multiple items by their IDs. This action is irreversible.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"keys": {
"type": "array",
"items": {"type": "string"},
"description": "Array of item IDs to delete",
},
},
"required": ["collection", "keys"],
},
"scope": "admin",
},
{
"name": "search_items",
"method_name": "search_items",
"description": "Full-text search across items in a collection.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"query": {"type": "string", "description": "Search query"},
"fields": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Fields to return",
},
"limit": {
"type": "integer",
"description": "Maximum items to return",
"default": 25,
},
},
"required": ["collection", "query"],
},
"scope": "read",
},
{
"name": "aggregate_items",
"method_name": "aggregate_items",
"description": "Perform aggregate operations (count, sum, avg, min, max) on items.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"aggregate": {
"type": "object",
"description": 'Aggregate functions (e.g., {"count": "*", "sum": "price"})',
},
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Filter to apply before aggregation",
},
"groupBy": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Fields to group by",
},
},
"required": ["collection", "aggregate"],
},
"scope": "read",
},
{
"name": "export_items",
"method_name": "export_items",
"description": "Export items from a collection with filtering options.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"fields": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Fields to export",
},
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Filter to apply",
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields",
},
"limit": {
"type": "integer",
"description": "Maximum items to export",
"default": 1000,
},
},
"required": ["collection"],
},
"scope": "read",
},
{
"name": "import_items",
"method_name": "import_items",
"description": "Import multiple items into a collection.",
"schema": {
"type": "object",
"properties": {
"collection": {"type": "string", "description": "Collection name"},
"data": {
"type": "array",
"items": {"type": "object"},
"description": "Array of items to import",
},
},
"required": ["collection", "data"],
},
"scope": "write",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_items(
client: DirectusClient,
collection: str,
fields: list[str] | None = None,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
offset: int = 0,
search: str | None = None,
) -> str:
"""List items from a collection."""
try:
# Parse JSON string parameters
parsed_fields = _parse_json_param(fields, "fields")
parsed_filter = _parse_json_param(filter, "filter")
parsed_sort = _parse_json_param(sort, "sort")
result = await client.list_items(
collection=collection,
fields=parsed_fields,
filter=parsed_filter,
sort=parsed_sort,
limit=limit,
offset=offset,
search=search,
)
items = result.get("data", [])
return json.dumps(
{"success": True, "collection": collection, "total": len(items), "items": items},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_item(
client: DirectusClient, collection: str, id: str, fields: list[str] | None = None
) -> str:
"""Get item by ID."""
try:
# Parse JSON string parameter
parsed_fields = _parse_json_param(fields, "fields")
result = await client.get_item(collection, id, fields=parsed_fields)
return json.dumps(
{"success": True, "item": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_item(
client: DirectusClient, collection: str, data: dict[str, Any], fields: list[str] | None = None
) -> str:
"""Create a new item."""
try:
# Parse JSON string parameters
parsed_data = _parse_json_param(data, "data")
parsed_fields = _parse_json_param(fields, "fields")
result = await client.create_item(collection, parsed_data, fields=parsed_fields)
return json.dumps(
{
"success": True,
"message": f"Item created in {collection}",
"item": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_items(
client: DirectusClient,
collection: str,
data: list[dict[str, Any]],
fields: list[str] | None = None,
) -> str:
"""Create multiple items."""
try:
# Parse JSON string parameters
parsed_data = _parse_json_param(data, "data")
parsed_fields = _parse_json_param(fields, "fields")
result = await client.create_items(collection, parsed_data, fields=parsed_fields)
items = result.get("data", [])
return json.dumps(
{
"success": True,
"message": f"Created {len(items)} items in {collection}",
"items": items,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_item(
client: DirectusClient,
collection: str,
id: str,
data: dict[str, Any],
fields: list[str] | None = None,
) -> str:
"""Update an item."""
try:
# Parse JSON string parameters
parsed_data = _parse_json_param(data, "data")
parsed_fields = _parse_json_param(fields, "fields")
result = await client.update_item(collection, id, parsed_data, fields=parsed_fields)
return json.dumps(
{"success": True, "message": f"Item {id} updated", "item": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_items(
client: DirectusClient,
collection: str,
keys: list[str],
data: dict[str, Any],
fields: list[str] | None = None,
) -> str:
"""Update multiple items."""
try:
# Parse JSON string parameters
parsed_keys = _parse_json_param(keys, "keys")
parsed_data = _parse_json_param(data, "data")
parsed_fields = _parse_json_param(fields, "fields")
result = await client.update_items(
collection, parsed_keys, parsed_data, fields=parsed_fields
)
items = result.get("data", [])
return json.dumps(
{
"success": True,
"message": f"Updated {len(items)} items in {collection}",
"items": items,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_item(client: DirectusClient, collection: str, id: str) -> str:
"""Delete an item."""
try:
await client.delete_item(collection, id)
return json.dumps(
{"success": True, "message": f"Item {id} deleted from {collection}"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_items(client: DirectusClient, collection: str, keys: list[str]) -> str:
"""Delete multiple items."""
try:
# Parse JSON string parameter
parsed_keys = _parse_json_param(keys, "keys")
await client.delete_items(collection, parsed_keys)
return json.dumps(
{"success": True, "message": f"Deleted {len(keys)} items from {collection}"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def search_items(
client: DirectusClient,
collection: str,
query: str,
fields: list[str] | None = None,
limit: int = 25,
) -> str:
"""Full-text search items."""
try:
# Parse JSON string parameter
parsed_fields = _parse_json_param(fields, "fields")
result = await client.list_items(
collection=collection, fields=parsed_fields, search=query, limit=limit
)
items = result.get("data", [])
return json.dumps(
{"success": True, "query": query, "total": len(items), "items": items},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def aggregate_items(
client: DirectusClient,
collection: str,
aggregate: dict[str, Any],
filter: dict | None = None,
groupBy: list[str] | None = None,
) -> str:
"""Aggregate items."""
try:
# Parse JSON string parameters
parsed_aggregate = _parse_json_param(aggregate, "aggregate")
parsed_filter = _parse_json_param(filter, "filter")
_parse_json_param(groupBy, "groupBy")
result = await client.list_items(
collection=collection, filter=parsed_filter, aggregate=parsed_aggregate
)
return json.dumps(
{"success": True, "collection": collection, "result": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def export_items(
client: DirectusClient,
collection: str,
fields: list[str] | None = None,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 1000,
) -> str:
"""Export items from a collection."""
try:
# Parse JSON string parameters
parsed_fields = _parse_json_param(fields, "fields")
parsed_filter = _parse_json_param(filter, "filter")
parsed_sort = _parse_json_param(sort, "sort")
result = await client.list_items(
collection=collection,
fields=parsed_fields,
filter=parsed_filter,
sort=parsed_sort,
limit=limit,
)
items = result.get("data", [])
return json.dumps(
{
"success": True,
"collection": collection,
"exported_count": len(items),
"data": items,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def import_items(client: DirectusClient, collection: str, data: list[dict[str, Any]]) -> str:
"""Import items into a collection."""
try:
# Parse JSON string parameter
parsed_data = _parse_json_param(data, "data")
result = await client.create_items(collection, parsed_data)
items = result.get("data", [])
return json.dumps(
{
"success": True,
"message": f"Imported {len(items)} items into {collection}",
"imported_count": len(items),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,259 @@
"""
System Handler - Settings, Server, Schema, Activity, Presets, Notifications
Phase J.4: 10 tools
- Settings: get, update (2)
- Server: info, health, graphql_sdl, openapi_spec (4)
- Schema: snapshot, diff, apply (3)
- Activity: list (1)
"""
import json
from typing import Any
from plugins.directus.client import DirectusClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (10 tools)"""
return [
# =====================
# SETTINGS (2)
# =====================
{
"name": "get_settings",
"method_name": "get_settings",
"description": "Get system settings.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "update_settings",
"method_name": "update_settings",
"description": "Update system settings (project name, logo, colors, etc.).",
"schema": {
"type": "object",
"properties": {"data": {"type": "object", "description": "Settings to update"}},
"required": ["data"],
},
"scope": "admin",
},
# =====================
# SERVER (4)
# =====================
{
"name": "get_server_info",
"method_name": "get_server_info",
"description": "Get Directus server information (version, extensions, etc.).",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "health_check",
"method_name": "health_check",
"description": "Check Directus server health status.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "get_graphql_sdl",
"method_name": "get_graphql_sdl",
"description": "Get the GraphQL SDL schema.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "get_openapi_spec",
"method_name": "get_openapi_spec",
"description": "Get the OpenAPI specification.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
# =====================
# SCHEMA (3)
# =====================
{
"name": "get_schema_snapshot",
"method_name": "get_schema_snapshot",
"description": "Get complete schema snapshot for migration/backup.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "admin",
},
{
"name": "schema_diff",
"method_name": "schema_diff",
"description": "Get diff between current schema and provided snapshot.",
"schema": {
"type": "object",
"properties": {
"snapshot": {
"type": "object",
"description": "Schema snapshot to compare against",
}
},
"required": ["snapshot"],
},
"scope": "admin",
},
{
"name": "schema_apply",
"method_name": "schema_apply",
"description": "Apply schema diff to database.",
"schema": {
"type": "object",
"properties": {"diff": {"type": "object", "description": "Schema diff to apply"}},
"required": ["diff"],
},
"scope": "admin",
},
# =====================
# ACTIVITY (1)
# =====================
{
"name": "list_activity",
"method_name": "list_activity",
"description": "List activity log (all actions performed in Directus).",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Filter object (e.g., {"action": {"_eq": "create"}})',
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields (default: ['-timestamp'])",
},
"limit": {
"type": "integer",
"description": "Maximum activities to return",
"default": 100,
},
},
"required": [],
},
"scope": "read",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def get_settings(client: DirectusClient) -> str:
"""Get system settings."""
try:
result = await client.get_settings()
return json.dumps(
{"success": True, "settings": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_settings(client: DirectusClient, data: dict[str, Any]) -> str:
"""Update system settings."""
try:
result = await client.update_settings(data)
return json.dumps(
{"success": True, "message": "Settings updated", "settings": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_server_info(client: DirectusClient) -> str:
"""Get server info."""
try:
result = await client.get_server_info()
return json.dumps(
{"success": True, "server": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def health_check(client: DirectusClient) -> str:
"""Check server health."""
try:
result = await client.health_check()
return json.dumps(
{"success": True, "status": result.get("status", "unknown"), "health": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_graphql_sdl(client: DirectusClient) -> str:
"""Get GraphQL SDL."""
try:
result = await client.get_graphql_sdl()
# GraphQL SDL is usually returned as text
if isinstance(result, dict):
sdl = result.get("data", result)
else:
sdl = result
return json.dumps({"success": True, "sdl": sdl}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_openapi_spec(client: DirectusClient) -> str:
"""Get OpenAPI specification."""
try:
result = await client.get_openapi_spec()
return json.dumps({"success": True, "spec": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_schema_snapshot(client: DirectusClient) -> str:
"""Get schema snapshot."""
try:
result = await client.get_schema_snapshot()
return json.dumps(
{"success": True, "snapshot": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def schema_diff(client: DirectusClient, snapshot: dict[str, Any]) -> str:
"""Get schema diff."""
try:
result = await client.schema_diff(snapshot)
return json.dumps(
{"success": True, "diff": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def schema_apply(client: DirectusClient, diff: dict[str, Any]) -> str:
"""Apply schema diff."""
try:
result = await client.schema_apply(diff)
return json.dumps(
{
"success": True,
"message": "Schema applied",
"result": result.get("data") if result else None,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_activity(
client: DirectusClient,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
) -> str:
"""List activity log."""
try:
result = await client.list_activity(filter=filter, sort=sort, limit=limit)
activities = result.get("data", [])
return json.dumps(
{"success": True, "total": len(activities), "activities": activities},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,357 @@
"""
Users Handler - User management
Phase J.2: 10 tools
- list_users, get_user, get_current_user
- create_user, update_user, delete_user, delete_users
- invite_user, accept_invite, update_current_user
"""
import json
from typing import Any
from plugins.directus.client import DirectusClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (10 tools)"""
return [
{
"name": "list_users",
"method_name": "list_users",
"description": "List all users in Directus with filtering options.",
"schema": {
"type": "object",
"properties": {
"filter": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Filter object (e.g., {"status": {"_eq": "active"}})',
},
"sort": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Sort fields (e.g., ['email'])",
},
"limit": {
"type": "integer",
"description": "Maximum users to return",
"default": 100,
},
"offset": {"type": "integer", "description": "Users to skip", "default": 0},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_user",
"method_name": "get_user",
"description": "Get user details by ID.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "User UUID"}},
"required": ["id"],
},
"scope": "read",
},
{
"name": "get_current_user",
"method_name": "get_current_user",
"description": "Get the currently authenticated user.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "create_user",
"method_name": "create_user",
"description": "Create a new user.",
"schema": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"description": "User email address",
},
"password": {"type": "string", "minLength": 8, "description": "User password"},
"role": {"type": "string", "description": "Role UUID"},
"first_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "First name",
},
"last_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Last name",
},
"status": {
"type": "string",
"enum": ["draft", "invited", "active", "suspended", "archived"],
"default": "active",
"description": "User status",
},
},
"required": ["email", "password", "role"],
},
"scope": "admin",
},
{
"name": "update_user",
"method_name": "update_user",
"description": "Update user details.",
"schema": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "User UUID"},
"data": {
"type": "object",
"description": "Fields to update (email, first_name, last_name, role, status, etc.)",
},
},
"required": ["id", "data"],
},
"scope": "admin",
},
{
"name": "delete_user",
"method_name": "delete_user",
"description": "Delete a user. This action is irreversible.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "User UUID to delete"}},
"required": ["id"],
},
"scope": "admin",
},
{
"name": "delete_users",
"method_name": "delete_users",
"description": "Delete multiple users. This action is irreversible.",
"schema": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"items": {"type": "string"},
"description": "Array of user UUIDs to delete",
}
},
"required": ["ids"],
},
"scope": "admin",
},
{
"name": "invite_user",
"method_name": "invite_user",
"description": "Invite a user by email.",
"schema": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"description": "Email to invite",
},
"role": {"type": "string", "description": "Role UUID for the invited user"},
"invite_url": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Custom invite URL",
},
},
"required": ["email", "role"],
},
"scope": "admin",
},
{
"name": "update_current_user",
"method_name": "update_current_user",
"description": "Update the currently authenticated user's profile.",
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"description": "Fields to update (first_name, last_name, language, theme, etc.)",
}
},
"required": ["data"],
},
"scope": "write",
},
{
"name": "get_user_role",
"method_name": "get_user_role",
"description": "Get the role of a specific user.",
"schema": {
"type": "object",
"properties": {"id": {"type": "string", "description": "User UUID"}},
"required": ["id"],
},
"scope": "read",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_users(
client: DirectusClient,
filter: dict | None = None,
sort: list[str] | None = None,
limit: int = 100,
offset: int = 0,
search: str | None = None,
) -> str:
"""List users."""
try:
result = await client.list_users(
filter=filter, sort=sort, limit=limit, offset=offset, search=search
)
users = result.get("data", [])
return json.dumps(
{"success": True, "total": len(users), "users": users}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_user(client: DirectusClient, id: str) -> str:
"""Get user by ID."""
try:
result = await client.get_user(id)
return json.dumps(
{"success": True, "user": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_current_user(client: DirectusClient) -> str:
"""Get current user."""
try:
result = await client.get_current_user()
return json.dumps(
{"success": True, "user": result.get("data")}, indent=2, ensure_ascii=False
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_user(
client: DirectusClient,
email: str,
password: str,
role: str,
first_name: str | None = None,
last_name: str | None = None,
status: str = "active",
) -> str:
"""Create a new user."""
try:
result = await client.create_user(
email=email,
password=password,
role=role,
first_name=first_name,
last_name=last_name,
status=status,
)
return json.dumps(
{"success": True, "message": f"User {email} created", "user": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_user(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
"""Update user."""
try:
result = await client.update_user(id, data)
return json.dumps(
{"success": True, "message": "User updated", "user": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_user(client: DirectusClient, id: str) -> str:
"""Delete a user."""
try:
await client.delete_user(id)
return json.dumps({"success": True, "message": f"User {id} deleted"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_users(client: DirectusClient, ids: list[str]) -> str:
"""Delete multiple users."""
try:
await client.delete_users(ids)
return json.dumps({"success": True, "message": f"Deleted {len(ids)} users"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def invite_user(
client: DirectusClient, email: str, role: str, invite_url: str | None = None
) -> str:
"""Invite a user."""
try:
result = await client.invite_user(email, role, invite_url)
return json.dumps(
{
"success": True,
"message": f"Invitation sent to {email}",
"result": result.get("data"),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_current_user(client: DirectusClient, data: dict[str, Any]) -> str:
"""Update current user profile."""
try:
# Get current user first to get ID
current = await client.get_current_user()
user_id = current.get("data", {}).get("id")
if not user_id:
return json.dumps(
{"success": False, "error": "Cannot determine current user"}, indent=2
)
result = await client.update_user(user_id, data)
return json.dumps(
{"success": True, "message": "Profile updated", "user": result.get("data")},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_user_role(client: DirectusClient, id: str) -> str:
"""Get user's role."""
try:
result = await client.get_user(id)
user = result.get("data", {})
role_id = user.get("role")
if role_id:
role_result = await client.get_role(role_id)
return json.dumps(
{"success": True, "user_id": id, "role": role_result.get("data")},
indent=2,
ensure_ascii=False,
)
else:
return json.dumps(
{
"success": True,
"user_id": id,
"role": None,
"message": "User has no role assigned",
},
indent=2,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)