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,6 @@
"""Appwrite Plugin - Self-Hosted Backend-as-a-Service Management"""
from plugins.appwrite.client import AppwriteClient
from plugins.appwrite.plugin import AppwritePlugin
__all__ = ["AppwritePlugin", "AppwriteClient"]

1611
plugins/appwrite/client.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
"""Appwrite Plugin Handlers"""
from plugins.appwrite.handlers import (
databases,
documents,
# Phase I.4
functions,
messaging,
# Phase I.3
storage,
system,
teams,
# Phase I.2
users,
)
__all__ = [
"databases",
"documents",
"system",
# Phase I.2
"users",
"teams",
# Phase I.3
"storage",
# Phase I.4
"functions",
"messaging",
]

View File

@@ -0,0 +1,809 @@
"""
Databases Handler - manages Appwrite databases, collections, attributes, and indexes
Phase I.1: 18 tools
- Databases: 5 (list, get, create, update, delete)
- Collections: 5 (list, get, create, update, delete)
- Attributes: 5 (list, create_string, create_integer, create_boolean, delete)
- Indexes: 3 (list, create, delete)
"""
import json
from typing import Any
from plugins.appwrite.client import AppwriteClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (18 tools)"""
return [
# =====================
# DATABASES (5)
# =====================
{
"name": "list_databases",
"method_name": "list_databases",
"description": "List all databases in the Appwrite project. Returns database ID, name, and creation date.",
"schema": {
"type": "object",
"properties": {
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering (e.g., 'limit(25)', 'offset(0)')",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term to filter databases by name",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_database",
"method_name": "get_database",
"description": "Get database details by ID.",
"schema": {
"type": "object",
"properties": {"database_id": {"type": "string", "description": "Database ID"}},
"required": ["database_id"],
},
"scope": "read",
},
{
"name": "create_database",
"method_name": "create_database",
"description": "Create a new database. Use 'unique()' as database_id to auto-generate a unique ID.",
"schema": {
"type": "object",
"properties": {
"database_id": {
"type": "string",
"description": "Unique database ID. Use 'unique()' for auto-generation",
},
"name": {"type": "string", "description": "Database name"},
"enabled": {
"type": "boolean",
"description": "Enable or disable the database",
"default": True,
},
},
"required": ["database_id", "name"],
},
"scope": "write",
},
{
"name": "update_database",
"method_name": "update_database",
"description": "Update database name or enabled status.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"name": {"type": "string", "description": "New database name"},
"enabled": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Enable or disable the database",
},
},
"required": ["database_id", "name"],
},
"scope": "write",
},
{
"name": "delete_database",
"method_name": "delete_database",
"description": "Delete a database and all its collections. This action is irreversible.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID to delete"}
},
"required": ["database_id"],
},
"scope": "admin",
},
# =====================
# COLLECTIONS (5)
# =====================
{
"name": "list_collections",
"method_name": "list_collections",
"description": "List all collections in a database.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term to filter collections",
},
},
"required": ["database_id"],
},
"scope": "read",
},
{
"name": "get_collection",
"method_name": "get_collection",
"description": "Get collection details including attributes and indexes.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
},
"required": ["database_id", "collection_id"],
},
"scope": "read",
},
{
"name": "create_collection",
"method_name": "create_collection",
"description": "Create a new collection in a database. Use 'unique()' for auto-generated ID.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {
"type": "string",
"description": "Unique collection ID. Use 'unique()' for auto-generation",
},
"name": {"type": "string", "description": "Collection name"},
"permissions": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Collection permissions (e.g., 'read(\"any\")', 'write(\"users\")')",
},
"document_security": {
"type": "boolean",
"description": "Enable document-level security",
"default": True,
},
"enabled": {
"type": "boolean",
"description": "Enable or disable the collection",
"default": True,
},
},
"required": ["database_id", "collection_id", "name"],
},
"scope": "write",
},
{
"name": "update_collection",
"method_name": "update_collection",
"description": "Update collection settings.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"name": {"type": "string", "description": "New collection name"},
"permissions": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "New permissions",
},
"document_security": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Enable/disable document-level security",
},
"enabled": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Enable/disable the collection",
},
},
"required": ["database_id", "collection_id", "name"],
},
"scope": "write",
},
{
"name": "delete_collection",
"method_name": "delete_collection",
"description": "Delete a collection and all its documents. This action is irreversible.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID to delete"},
},
"required": ["database_id", "collection_id"],
},
"scope": "admin",
},
# =====================
# ATTRIBUTES (5)
# =====================
{
"name": "list_attributes",
"method_name": "list_attributes",
"description": "List all attributes (fields/columns) of a collection.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
},
"required": ["database_id", "collection_id"],
},
"scope": "read",
},
{
"name": "create_string_attribute",
"method_name": "create_string_attribute",
"description": "Create a string attribute in a collection.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"key": {"type": "string", "description": "Attribute key (field name)"},
"size": {
"type": "integer",
"description": "Maximum string length",
"default": 255,
},
"required": {
"type": "boolean",
"description": "Is this field required?",
"default": False,
},
"default": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Default value",
},
"array": {
"type": "boolean",
"description": "Is this an array attribute?",
"default": False,
},
"encrypt": {
"type": "boolean",
"description": "Encrypt the attribute value",
"default": False,
},
},
"required": ["database_id", "collection_id", "key"],
},
"scope": "write",
},
{
"name": "create_integer_attribute",
"method_name": "create_integer_attribute",
"description": "Create an integer attribute in a collection.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"key": {"type": "string", "description": "Attribute key (field name)"},
"required": {
"type": "boolean",
"description": "Is this field required?",
"default": False,
},
"min": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Minimum value",
},
"max": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Maximum value",
},
"default": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Default value",
},
"array": {
"type": "boolean",
"description": "Is this an array attribute?",
"default": False,
},
},
"required": ["database_id", "collection_id", "key"],
},
"scope": "write",
},
{
"name": "create_boolean_attribute",
"method_name": "create_boolean_attribute",
"description": "Create a boolean attribute in a collection.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"key": {"type": "string", "description": "Attribute key (field name)"},
"required": {
"type": "boolean",
"description": "Is this field required?",
"default": False,
},
"default": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Default value",
},
"array": {
"type": "boolean",
"description": "Is this an array attribute?",
"default": False,
},
},
"required": ["database_id", "collection_id", "key"],
},
"scope": "write",
},
{
"name": "delete_attribute",
"method_name": "delete_attribute",
"description": "Delete an attribute from a collection. This will remove the field from all documents.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"key": {"type": "string", "description": "Attribute key to delete"},
},
"required": ["database_id", "collection_id", "key"],
},
"scope": "admin",
},
# =====================
# INDEXES (3)
# =====================
{
"name": "list_indexes",
"method_name": "list_indexes",
"description": "List all indexes of a collection.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
},
"required": ["database_id", "collection_id"],
},
"scope": "read",
},
{
"name": "create_index",
"method_name": "create_index",
"description": "Create an index on collection attributes for faster queries.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"key": {"type": "string", "description": "Index key (unique name)"},
"type": {
"type": "string",
"enum": ["key", "unique", "fulltext"],
"description": "Index type: key (regular), unique, or fulltext",
},
"attributes": {
"type": "array",
"items": {"type": "string"},
"description": "Array of attribute keys to index",
},
"orders": {
"anyOf": [
{"type": "array", "items": {"type": "string", "enum": ["ASC", "DESC"]}},
{"type": "null"},
],
"description": "Sort order for each attribute (ASC or DESC)",
},
},
"required": ["database_id", "collection_id", "key", "type", "attributes"],
},
"scope": "write",
},
{
"name": "delete_index",
"method_name": "delete_index",
"description": "Delete an index from a collection.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"key": {"type": "string", "description": "Index key to delete"},
},
"required": ["database_id", "collection_id", "key"],
},
"scope": "admin",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_databases(
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
) -> str:
"""List all databases."""
try:
result = await client.list_databases(queries=queries, search=search)
databases = result.get("databases", [])
response = {
"success": True,
"total": result.get("total", len(databases)),
"databases": databases,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_database(client: AppwriteClient, database_id: str) -> str:
"""Get database by ID."""
try:
result = await client.get_database(database_id)
return json.dumps({"success": True, "database": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_database(
client: AppwriteClient, database_id: str, name: str, enabled: bool = True
) -> str:
"""Create a new database."""
try:
result = await client.create_database(database_id=database_id, name=name, enabled=enabled)
return json.dumps(
{
"success": True,
"message": f"Database '{name}' created successfully",
"database": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_database(
client: AppwriteClient, database_id: str, name: str, enabled: bool | None = None
) -> str:
"""Update database."""
try:
result = await client.update_database(database_id=database_id, name=name, enabled=enabled)
return json.dumps(
{"success": True, "message": "Database updated successfully", "database": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_database(client: AppwriteClient, database_id: str) -> str:
"""Delete database."""
try:
await client.delete_database(database_id)
return json.dumps(
{"success": True, "message": f"Database '{database_id}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_collections(
client: AppwriteClient,
database_id: str,
queries: list[str] | None = None,
search: str | None = None,
) -> str:
"""List collections in a database."""
try:
result = await client.list_collections(
database_id=database_id, queries=queries, search=search
)
collections = result.get("collections", [])
response = {
"success": True,
"database_id": database_id,
"total": result.get("total", len(collections)),
"collections": collections,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_collection(client: AppwriteClient, database_id: str, collection_id: str) -> str:
"""Get collection details."""
try:
result = await client.get_collection(database_id, collection_id)
return json.dumps({"success": True, "collection": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_collection(
client: AppwriteClient,
database_id: str,
collection_id: str,
name: str,
permissions: list[str] | None = None,
document_security: bool = True,
enabled: bool = True,
) -> str:
"""Create a new collection."""
try:
result = await client.create_collection(
database_id=database_id,
collection_id=collection_id,
name=name,
permissions=permissions,
document_security=document_security,
enabled=enabled,
)
return json.dumps(
{
"success": True,
"message": f"Collection '{name}' created successfully",
"collection": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_collection(
client: AppwriteClient,
database_id: str,
collection_id: str,
name: str,
permissions: list[str] | None = None,
document_security: bool | None = None,
enabled: bool | None = None,
) -> str:
"""Update collection."""
try:
result = await client.update_collection(
database_id=database_id,
collection_id=collection_id,
name=name,
permissions=permissions,
document_security=document_security,
enabled=enabled,
)
return json.dumps(
{"success": True, "message": "Collection updated successfully", "collection": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_collection(client: AppwriteClient, database_id: str, collection_id: str) -> str:
"""Delete collection."""
try:
await client.delete_collection(database_id, collection_id)
return json.dumps(
{"success": True, "message": f"Collection '{collection_id}' deleted successfully"},
indent=2,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_attributes(
client: AppwriteClient, database_id: str, collection_id: str, queries: list[str] | None = None
) -> str:
"""List collection attributes."""
try:
result = await client.list_attributes(
database_id=database_id, collection_id=collection_id, queries=queries
)
attributes = result.get("attributes", [])
response = {
"success": True,
"database_id": database_id,
"collection_id": collection_id,
"total": result.get("total", len(attributes)),
"attributes": attributes,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_string_attribute(
client: AppwriteClient,
database_id: str,
collection_id: str,
key: str,
size: int = 255,
required: bool = False,
default: str | None = None,
array: bool = False,
encrypt: bool = False,
) -> str:
"""Create string attribute."""
try:
result = await client.create_string_attribute(
database_id=database_id,
collection_id=collection_id,
key=key,
size=size,
required=required,
default=default,
array=array,
encrypt=encrypt,
)
return json.dumps(
{
"success": True,
"message": f"String attribute '{key}' created successfully",
"attribute": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_integer_attribute(
client: AppwriteClient,
database_id: str,
collection_id: str,
key: str,
required: bool = False,
min: int | None = None,
max: int | None = None,
default: int | None = None,
array: bool = False,
) -> str:
"""Create integer attribute."""
try:
result = await client.create_integer_attribute(
database_id=database_id,
collection_id=collection_id,
key=key,
required=required,
min=min,
max=max,
default=default,
array=array,
)
return json.dumps(
{
"success": True,
"message": f"Integer attribute '{key}' created successfully",
"attribute": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_boolean_attribute(
client: AppwriteClient,
database_id: str,
collection_id: str,
key: str,
required: bool = False,
default: bool | None = None,
array: bool = False,
) -> str:
"""Create boolean attribute."""
try:
result = await client.create_boolean_attribute(
database_id=database_id,
collection_id=collection_id,
key=key,
required=required,
default=default,
array=array,
)
return json.dumps(
{
"success": True,
"message": f"Boolean attribute '{key}' created successfully",
"attribute": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_attribute(
client: AppwriteClient, database_id: str, collection_id: str, key: str
) -> str:
"""Delete attribute."""
try:
await client.delete_attribute(database_id, collection_id, key)
return json.dumps(
{"success": True, "message": f"Attribute '{key}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_indexes(
client: AppwriteClient, database_id: str, collection_id: str, queries: list[str] | None = None
) -> str:
"""List collection indexes."""
try:
result = await client.list_indexes(
database_id=database_id, collection_id=collection_id, queries=queries
)
indexes = result.get("indexes", [])
response = {
"success": True,
"database_id": database_id,
"collection_id": collection_id,
"total": result.get("total", len(indexes)),
"indexes": indexes,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_index(
client: AppwriteClient,
database_id: str,
collection_id: str,
key: str,
type: str,
attributes: list[str],
orders: list[str] | None = None,
) -> str:
"""Create index."""
try:
result = await client.create_index(
database_id=database_id,
collection_id=collection_id,
key=key,
type=type,
attributes=attributes,
orders=orders,
)
return json.dumps(
{"success": True, "message": f"Index '{key}' created successfully", "index": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_index(
client: AppwriteClient, database_id: str, collection_id: str, key: str
) -> str:
"""Delete index."""
try:
await client.delete_index(database_id, collection_id, key)
return json.dumps(
{"success": True, "message": f"Index '{key}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,752 @@
"""
Documents Handler - manages Appwrite document CRUD operations
Phase I.1: 12 tools
- Documents CRUD: 5 (list, get, create, update, delete)
- Bulk Operations: 3 (bulk_create, bulk_update, bulk_delete)
- Query/Count: 4 (search, count, get_by_query, list_with_cursor)
"""
import json
from typing import Any
from plugins.appwrite.client import AppwriteClient
# =====================
# QUERY HELPERS (Appwrite 1.7.4 JSON format)
# =====================
def _query_limit(value: int) -> str:
"""Build limit query in JSON format."""
return json.dumps({"method": "limit", "values": [value]})
def _query_offset(value: int) -> str:
"""Build offset query in JSON format."""
return json.dumps({"method": "offset", "values": [value]})
def _query_order_asc(attribute: str) -> str:
"""Build orderAsc query in JSON format."""
return json.dumps({"method": "orderAsc", "values": [attribute]})
def _query_order_desc(attribute: str) -> str:
"""Build orderDesc query in JSON format."""
return json.dumps({"method": "orderDesc", "values": [attribute]})
def _query_cursor_after(document_id: str) -> str:
"""Build cursorAfter query in JSON format."""
return json.dumps({"method": "cursorAfter", "values": [document_id]})
def _query_cursor_before(document_id: str) -> str:
"""Build cursorBefore query in JSON format."""
return json.dumps({"method": "cursorBefore", "values": [document_id]})
def _query_search(attribute: str, value: str) -> str:
"""Build search query in JSON format."""
return json.dumps({"method": "search", "attribute": attribute, "values": [value]})
def _query_equal(attribute: str, values: list[Any]) -> str:
"""Build equal query in JSON format."""
return json.dumps({"method": "equal", "attribute": attribute, "values": values})
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (12 tools)"""
return [
# =====================
# DOCUMENT CRUD (5)
# =====================
{
"name": "list_documents",
"method_name": "list_documents",
"description": """List documents in a collection with powerful query support.
Queries must be JSON objects (Appwrite 1.7.4 format):
- {"method": "equal", "attribute": "status", "values": ["active"]}
- {"method": "notEqual", "attribute": "type", "values": ["draft"]}
- {"method": "greaterThan", "attribute": "price", "values": [100]}
- {"method": "search", "attribute": "title", "values": ["keyword"]}
- {"method": "orderAsc", "values": ["createdAt"]}
- {"method": "orderDesc", "values": ["createdAt"]}
- {"method": "limit", "values": [25]}
- {"method": "offset", "values": [50]}
- {"method": "cursorAfter", "values": ["documentId"]}""",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query JSON strings for filtering, sorting, and pagination",
"examples": [
[
'{"method":"equal","attribute":"status","values":["active"]}',
'{"method":"limit","values":[25]}',
]
],
},
},
"required": ["database_id", "collection_id"],
},
"scope": "read",
},
{
"name": "get_document",
"method_name": "get_document",
"description": "Get a single document by ID.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"document_id": {"type": "string", "description": "Document ID"},
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Optional queries for selecting specific attributes",
},
},
"required": ["database_id", "collection_id", "document_id"],
},
"scope": "read",
},
{
"name": "create_document",
"method_name": "create_document",
"description": "Create a new document. Use 'unique()' as document_id for auto-generated ID.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"document_id": {
"type": "string",
"description": "Unique document ID. Use 'unique()' for auto-generation",
},
"data": {
"type": "object",
"description": "Document data (key-value pairs matching collection attributes)",
},
"permissions": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Document permissions (e.g., 'read(\"user:123\")', 'write(\"team:456\")')",
},
},
"required": ["database_id", "collection_id", "document_id", "data"],
},
"scope": "write",
},
{
"name": "update_document",
"method_name": "update_document",
"description": "Update an existing document. Only provided fields will be updated.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"document_id": {"type": "string", "description": "Document ID"},
"data": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Fields to update",
},
"permissions": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "New permissions (replaces existing)",
},
},
"required": ["database_id", "collection_id", "document_id"],
},
"scope": "write",
},
{
"name": "delete_document",
"method_name": "delete_document",
"description": "Delete a document by ID.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"document_id": {"type": "string", "description": "Document ID to delete"},
},
"required": ["database_id", "collection_id", "document_id"],
},
"scope": "write",
},
# =====================
# BULK OPERATIONS (3)
# =====================
{
"name": "bulk_create_documents",
"method_name": "bulk_create_documents",
"description": """Create multiple documents at once. More efficient than creating one by one.
Example documents array:
[
{"document_id": "unique()", "data": {"title": "Doc 1", "status": "active"}},
{"document_id": "doc-2", "data": {"title": "Doc 2", "status": "draft"}, "permissions": ["read(\\"any\\")"]}
]
Each document must have:
- document_id: Use 'unique()' for auto-generated ID or provide your own
- data: Object with field values matching collection attributes""",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"documents": {
"type": "array",
"items": {
"type": "object",
"properties": {
"document_id": {
"type": "string",
"description": "Document ID. Use 'unique()' for auto-generation",
},
"data": {
"type": "object",
"description": "Document data (key-value pairs matching collection attributes)",
},
"permissions": {
"anyOf": [
{"type": "array", "items": {"type": "string"}},
{"type": "null"},
],
"description": "Optional permissions for this document",
},
},
"required": ["document_id", "data"],
},
"description": "Array of documents to create. Each must have document_id and data.",
},
},
"required": ["database_id", "collection_id", "documents"],
},
"scope": "write",
},
{
"name": "bulk_update_documents",
"method_name": "bulk_update_documents",
"description": "Update multiple documents at once.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"updates": {
"type": "array",
"items": {
"type": "object",
"properties": {
"document_id": {"type": "string"},
"data": {"type": "object"},
"permissions": {
"anyOf": [
{"type": "array", "items": {"type": "string"}},
{"type": "null"},
]
},
},
"required": ["document_id", "data"],
},
"description": "Array of document updates",
},
},
"required": ["database_id", "collection_id", "updates"],
},
"scope": "write",
},
{
"name": "bulk_delete_documents",
"method_name": "bulk_delete_documents",
"description": "Delete multiple documents at once.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"document_ids": {
"type": "array",
"items": {"type": "string"},
"description": "Array of document IDs to delete",
},
},
"required": ["database_id", "collection_id", "document_ids"],
},
"scope": "write",
},
# =====================
# QUERY/SEARCH (4)
# =====================
{
"name": "search_documents",
"method_name": "search_documents",
"description": "Full-text search in documents. Requires a fulltext index on the searched attribute.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"attribute": {
"type": "string",
"description": "Attribute name to search in (must have fulltext index)",
},
"query": {"type": "string", "description": "Search query string"},
"limit": {"type": "integer", "description": "Maximum results", "default": 25},
},
"required": ["database_id", "collection_id", "attribute", "query"],
},
"scope": "read",
},
{
"name": "count_documents",
"method_name": "count_documents",
"description": "Count documents in a collection with optional filters.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Optional filter queries",
},
},
"required": ["database_id", "collection_id"],
},
"scope": "read",
},
{
"name": "get_documents_by_ids",
"method_name": "get_documents_by_ids",
"description": "Get multiple documents by their IDs.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"document_ids": {
"type": "array",
"items": {"type": "string"},
"description": "Array of document IDs to fetch",
},
},
"required": ["database_id", "collection_id", "document_ids"],
},
"scope": "read",
},
{
"name": "list_documents_paginated",
"method_name": "list_documents_paginated",
"description": "List documents with cursor-based pagination for efficient large dataset traversal.",
"schema": {
"type": "object",
"properties": {
"database_id": {"type": "string", "description": "Database ID"},
"collection_id": {"type": "string", "description": "Collection ID"},
"limit": {
"type": "integer",
"description": "Results per page",
"default": 25,
"maximum": 100,
},
"cursor": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Cursor from previous response for next page",
},
"cursor_direction": {
"type": "string",
"enum": ["after", "before"],
"description": "Cursor direction",
"default": "after",
},
"order_attribute": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Attribute to order by (default: $id)",
},
"order_type": {
"type": "string",
"enum": ["ASC", "DESC"],
"description": "Sort order",
"default": "DESC",
},
"filters": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Additional filter queries",
},
},
"required": ["database_id", "collection_id"],
},
"scope": "read",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_documents(
client: AppwriteClient, database_id: str, collection_id: str, queries: list[str] | None = None
) -> str:
"""List documents with queries."""
try:
result = await client.list_documents(
database_id=database_id, collection_id=collection_id, queries=queries
)
documents = result.get("documents", [])
response = {
"success": True,
"database_id": database_id,
"collection_id": collection_id,
"total": result.get("total", len(documents)),
"documents": documents,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_document(
client: AppwriteClient,
database_id: str,
collection_id: str,
document_id: str,
queries: list[str] | None = None,
) -> str:
"""Get document by ID."""
try:
result = await client.get_document(
database_id=database_id,
collection_id=collection_id,
document_id=document_id,
queries=queries,
)
return json.dumps({"success": True, "document": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_document(
client: AppwriteClient,
database_id: str,
collection_id: str,
document_id: str,
data: dict[str, Any],
permissions: list[str] | None = None,
) -> str:
"""Create a new document."""
try:
result = await client.create_document(
database_id=database_id,
collection_id=collection_id,
document_id=document_id,
data=data,
permissions=permissions,
)
return json.dumps(
{"success": True, "message": "Document created successfully", "document": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_document(
client: AppwriteClient,
database_id: str,
collection_id: str,
document_id: str,
data: dict[str, Any] | None = None,
permissions: list[str] | None = None,
) -> str:
"""Update document."""
try:
result = await client.update_document(
database_id=database_id,
collection_id=collection_id,
document_id=document_id,
data=data,
permissions=permissions,
)
return json.dumps(
{"success": True, "message": "Document updated successfully", "document": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_document(
client: AppwriteClient, database_id: str, collection_id: str, document_id: str
) -> str:
"""Delete document."""
try:
await client.delete_document(database_id, collection_id, document_id)
return json.dumps(
{"success": True, "message": f"Document '{document_id}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def bulk_create_documents(
client: AppwriteClient, database_id: str, collection_id: str, documents: list[dict[str, Any]]
) -> str:
"""Create multiple documents."""
try:
results = []
errors = []
for doc in documents:
try:
result = await client.create_document(
database_id=database_id,
collection_id=collection_id,
document_id=doc.get("document_id", "unique()"),
data=doc["data"],
permissions=doc.get("permissions"),
)
results.append({"id": result.get("$id"), "success": True})
except Exception as e:
errors.append({"document_id": doc.get("document_id"), "error": str(e)})
response = {
"success": len(errors) == 0,
"created": len(results),
"failed": len(errors),
"results": results,
}
if errors:
response["errors"] = errors
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def bulk_update_documents(
client: AppwriteClient, database_id: str, collection_id: str, updates: list[dict[str, Any]]
) -> str:
"""Update multiple documents."""
try:
results = []
errors = []
for update in updates:
try:
result = await client.update_document(
database_id=database_id,
collection_id=collection_id,
document_id=update["document_id"],
data=update.get("data"),
permissions=update.get("permissions"),
)
results.append({"id": result.get("$id"), "success": True})
except Exception as e:
errors.append({"document_id": update["document_id"], "error": str(e)})
response = {
"success": len(errors) == 0,
"updated": len(results),
"failed": len(errors),
"results": results,
}
if errors:
response["errors"] = errors
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def bulk_delete_documents(
client: AppwriteClient, database_id: str, collection_id: str, document_ids: list[str]
) -> str:
"""Delete multiple documents."""
try:
results = []
errors = []
for doc_id in document_ids:
try:
await client.delete_document(database_id, collection_id, doc_id)
results.append({"id": doc_id, "success": True})
except Exception as e:
errors.append({"document_id": doc_id, "error": str(e)})
response = {
"success": len(errors) == 0,
"deleted": len(results),
"failed": len(errors),
"results": results,
}
if errors:
response["errors"] = errors
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def search_documents(
client: AppwriteClient,
database_id: str,
collection_id: str,
attribute: str,
query: str,
limit: int = 25,
) -> str:
"""Full-text search in documents."""
try:
# Appwrite 1.7.4 JSON format for queries
queries = [_query_search(attribute, query), _query_limit(limit)]
result = await client.list_documents(
database_id=database_id, collection_id=collection_id, queries=queries
)
documents = result.get("documents", [])
response = {
"success": True,
"query": query,
"attribute": attribute,
"total": result.get("total", len(documents)),
"documents": documents,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
error_msg = str(e)
if "fulltext" in error_msg.lower() or "index" in error_msg.lower():
error_msg += " (Hint: Make sure you have a fulltext index on the searched attribute)"
return json.dumps({"success": False, "error": error_msg}, indent=2)
async def count_documents(
client: AppwriteClient, database_id: str, collection_id: str, queries: list[str] | None = None
) -> str:
"""Count documents in collection."""
try:
# Build query list: start with user filters, add limit(1) to minimize data
count_queries = []
# Add user-provided filter queries (make a copy to avoid mutating input)
if queries and len(queries) > 0:
count_queries.extend(queries)
# Always add limit(1) to minimize data transfer - Appwrite 1.7.4 JSON format
count_queries.append(_query_limit(1))
result = await client.list_documents(
database_id=database_id, collection_id=collection_id, queries=count_queries
)
response = {
"success": True,
"database_id": database_id,
"collection_id": collection_id,
"count": result.get("total", 0),
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_documents_by_ids(
client: AppwriteClient, database_id: str, collection_id: str, document_ids: list[str]
) -> str:
"""Get multiple documents by IDs."""
try:
documents = []
errors = []
for doc_id in document_ids:
try:
doc = await client.get_document(
database_id=database_id, collection_id=collection_id, document_id=doc_id
)
documents.append(doc)
except Exception as e:
errors.append({"document_id": doc_id, "error": str(e)})
response = {
"success": len(errors) == 0,
"found": len(documents),
"not_found": len(errors),
"documents": documents,
}
if errors:
response["errors"] = errors
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_documents_paginated(
client: AppwriteClient,
database_id: str,
collection_id: str,
limit: int = 25,
cursor: str | None = None,
cursor_direction: str = "after",
order_attribute: str | None = None,
order_type: str = "DESC",
filters: list[str] | None = None,
) -> str:
"""List documents with cursor pagination."""
try:
# Build query list (don't mutate input) - Appwrite 1.7.4 JSON format
queries = []
# Add user-provided filter queries
if filters and len(filters) > 0:
queries.extend(filters)
# Add ordering only if explicitly specified
if order_attribute:
if order_type == "DESC":
queries.append(_query_order_desc(order_attribute))
else:
queries.append(_query_order_asc(order_attribute))
# Add cursor
if cursor:
if cursor_direction == "after":
queries.append(_query_cursor_after(cursor))
else:
queries.append(_query_cursor_before(cursor))
# Add limit only if queries exist or limit differs from default
if queries or limit != 25:
queries.append(_query_limit(min(limit, 100)))
result = await client.list_documents(
database_id=database_id,
collection_id=collection_id,
queries=queries if queries else None,
)
documents = result.get("documents", [])
# Determine next cursor
next_cursor = None
if documents and len(documents) == limit:
next_cursor = documents[-1].get("$id")
response = {
"success": True,
"total": result.get("total", 0),
"count": len(documents),
"documents": documents,
"pagination": {
"limit": limit,
"cursor": cursor,
"next_cursor": next_cursor,
"has_more": next_cursor is not None,
},
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,629 @@
"""
Functions Handler - manages Appwrite serverless functions
Phase I.4: 14 tools
- Functions: 5 (list, get, create, update, delete)
- Deployments: 5 (list, get, create, delete, activate)
- Executions: 4 (list, get, create, delete)
"""
import json
from typing import Any
from plugins.appwrite.client import AppwriteClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (14 tools)"""
return [
# =====================
# FUNCTIONS (5)
# =====================
{
"name": "list_functions",
"method_name": "list_functions",
"description": "List all serverless functions in the project.",
"schema": {
"type": "object",
"properties": {
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term to filter functions",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_function",
"method_name": "get_function",
"description": "Get function details including deployment status and configuration.",
"schema": {
"type": "object",
"properties": {"function_id": {"type": "string", "description": "Function ID"}},
"required": ["function_id"],
},
"scope": "read",
},
{
"name": "create_function",
"method_name": "create_function",
"description": "Create a new serverless function.",
"schema": {
"type": "object",
"properties": {
"function_id": {
"type": "string",
"description": "Unique function ID. Use 'unique()' for auto-generation",
},
"name": {"type": "string", "description": "Function name"},
"runtime": {
"type": "string",
"description": "Runtime (e.g., 'node-18.0', 'python-3.9', 'php-8.0', 'ruby-3.0')",
"examples": [
"node-18.0",
"node-20.0",
"python-3.9",
"python-3.11",
"php-8.0",
"php-8.2",
"ruby-3.0",
"go-1.21",
"dart-3.0",
"rust-1.70",
],
},
"execute": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Execution permissions (e.g., ['any', 'users'])",
},
"events": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Events to trigger function (e.g., ['databases.*.collections.*.documents.*.create'])",
},
"schedule": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Cron schedule (e.g., '0 * * * *' for every hour)",
},
"timeout": {
"type": "integer",
"description": "Execution timeout in seconds",
"default": 15,
"minimum": 1,
"maximum": 900,
},
"enabled": {
"type": "boolean",
"description": "Enable function",
"default": True,
},
"logging": {
"type": "boolean",
"description": "Enable logging",
"default": True,
},
"entrypoint": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Entrypoint file (e.g., 'src/main.js')",
},
"commands": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Build commands",
},
},
"required": ["function_id", "name", "runtime"],
},
"scope": "write",
},
{
"name": "update_function",
"method_name": "update_function",
"description": "Update function configuration.",
"schema": {
"type": "object",
"properties": {
"function_id": {"type": "string", "description": "Function ID"},
"name": {"type": "string", "description": "Function name"},
"runtime": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New runtime",
},
"execute": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "New execution permissions",
},
"events": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "New trigger events",
},
"schedule": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New cron schedule",
},
"timeout": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "New timeout",
},
"enabled": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Enable/disable function",
},
},
"required": ["function_id", "name"],
},
"scope": "write",
},
{
"name": "delete_function",
"method_name": "delete_function",
"description": "Delete a function and all its deployments.",
"schema": {
"type": "object",
"properties": {
"function_id": {"type": "string", "description": "Function ID to delete"}
},
"required": ["function_id"],
},
"scope": "admin",
},
# =====================
# DEPLOYMENTS (5)
# =====================
{
"name": "list_deployments",
"method_name": "list_deployments",
"description": "List all deployments of a function.",
"schema": {
"type": "object",
"properties": {
"function_id": {"type": "string", "description": "Function ID"},
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term",
},
},
"required": ["function_id"],
},
"scope": "read",
},
{
"name": "get_deployment",
"method_name": "get_deployment",
"description": "Get deployment details including build status and logs.",
"schema": {
"type": "object",
"properties": {
"function_id": {"type": "string", "description": "Function ID"},
"deployment_id": {"type": "string", "description": "Deployment ID"},
},
"required": ["function_id", "deployment_id"],
},
"scope": "read",
},
{
"name": "delete_deployment",
"method_name": "delete_deployment",
"description": "Delete a deployment.",
"schema": {
"type": "object",
"properties": {
"function_id": {"type": "string", "description": "Function ID"},
"deployment_id": {"type": "string", "description": "Deployment ID to delete"},
},
"required": ["function_id", "deployment_id"],
},
"scope": "write",
},
{
"name": "activate_deployment",
"method_name": "activate_deployment",
"description": "Activate a deployment (set as the active version for the function).",
"schema": {
"type": "object",
"properties": {
"function_id": {"type": "string", "description": "Function ID"},
"deployment_id": {"type": "string", "description": "Deployment ID to activate"},
},
"required": ["function_id", "deployment_id"],
},
"scope": "write",
},
{
"name": "get_active_deployment",
"method_name": "get_active_deployment",
"description": "Get the currently active deployment for a function.",
"schema": {
"type": "object",
"properties": {"function_id": {"type": "string", "description": "Function ID"}},
"required": ["function_id"],
},
"scope": "read",
},
# =====================
# EXECUTIONS (4)
# =====================
{
"name": "list_executions",
"method_name": "list_executions",
"description": "List execution history for a function.",
"schema": {
"type": "object",
"properties": {
"function_id": {"type": "string", "description": "Function ID"},
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term",
},
},
"required": ["function_id"],
},
"scope": "read",
},
{
"name": "get_execution",
"method_name": "get_execution",
"description": "Get execution details including response, logs, and timing.",
"schema": {
"type": "object",
"properties": {
"function_id": {"type": "string", "description": "Function ID"},
"execution_id": {"type": "string", "description": "Execution ID"},
},
"required": ["function_id", "execution_id"],
},
"scope": "read",
},
{
"name": "execute_function",
"method_name": "execute_function",
"description": "Execute a function immediately.",
"schema": {
"type": "object",
"properties": {
"function_id": {"type": "string", "description": "Function ID"},
"body": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Request body (JSON string)",
},
"async_execution": {
"type": "boolean",
"description": "Run asynchronously (don't wait for response)",
"default": False,
},
"path": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Custom execution path",
},
"method": {
"type": "string",
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
"description": "HTTP method",
"default": "POST",
},
"headers": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Custom headers",
},
},
"required": ["function_id"],
},
"scope": "write",
},
{
"name": "delete_execution",
"method_name": "delete_execution",
"description": "Delete an execution log entry.",
"schema": {
"type": "object",
"properties": {
"function_id": {"type": "string", "description": "Function ID"},
"execution_id": {"type": "string", "description": "Execution ID to delete"},
},
"required": ["function_id", "execution_id"],
},
"scope": "write",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_functions(
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
) -> str:
"""List all functions."""
try:
result = await client.list_functions(queries=queries, search=search)
functions = result.get("functions", [])
response = {
"success": True,
"total": result.get("total", len(functions)),
"functions": functions,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_function(client: AppwriteClient, function_id: str) -> str:
"""Get function by ID."""
try:
result = await client.get_function(function_id)
return json.dumps({"success": True, "function": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_function(
client: AppwriteClient,
function_id: str,
name: str,
runtime: str,
execute: list[str] | None = None,
events: list[str] | None = None,
schedule: str | None = None,
timeout: int = 15,
enabled: bool = True,
logging: bool = True,
entrypoint: str | None = None,
commands: str | None = None,
) -> str:
"""Create a new function."""
try:
result = await client.create_function(
function_id=function_id,
name=name,
runtime=runtime,
execute=execute,
events=events,
schedule=schedule,
timeout=timeout,
enabled=enabled,
logging=logging,
entrypoint=entrypoint,
commands=commands,
)
return json.dumps(
{
"success": True,
"message": f"Function '{name}' created successfully",
"function": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_function(
client: AppwriteClient,
function_id: str,
name: str,
runtime: str | None = None,
execute: list[str] | None = None,
events: list[str] | None = None,
schedule: str | None = None,
timeout: int | None = None,
enabled: bool | None = None,
) -> str:
"""Update function."""
try:
result = await client.update_function(
function_id=function_id,
name=name,
runtime=runtime,
execute=execute,
events=events,
schedule=schedule,
timeout=timeout,
enabled=enabled,
)
return json.dumps(
{"success": True, "message": "Function updated successfully", "function": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_function(client: AppwriteClient, function_id: str) -> str:
"""Delete function."""
try:
await client.delete_function(function_id)
return json.dumps(
{"success": True, "message": f"Function '{function_id}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_deployments(
client: AppwriteClient,
function_id: str,
queries: list[str] | None = None,
search: str | None = None,
) -> str:
"""List function deployments."""
try:
result = await client.list_deployments(
function_id=function_id, queries=queries, search=search
)
deployments = result.get("deployments", [])
response = {
"success": True,
"function_id": function_id,
"total": result.get("total", len(deployments)),
"deployments": deployments,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_deployment(client: AppwriteClient, function_id: str, deployment_id: str) -> str:
"""Get deployment by ID."""
try:
result = await client.get_deployment(function_id, deployment_id)
return json.dumps({"success": True, "deployment": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_deployment(client: AppwriteClient, function_id: str, deployment_id: str) -> str:
"""Delete deployment."""
try:
await client.delete_deployment(function_id, deployment_id)
return json.dumps(
{"success": True, "message": f"Deployment '{deployment_id}' deleted successfully"},
indent=2,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def activate_deployment(client: AppwriteClient, function_id: str, deployment_id: str) -> str:
"""Activate deployment."""
try:
result = await client.update_deployment(function_id, deployment_id)
return json.dumps(
{
"success": True,
"message": f"Deployment '{deployment_id}' activated successfully",
"deployment": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_active_deployment(client: AppwriteClient, function_id: str) -> str:
"""Get active deployment for function."""
try:
func = await client.get_function(function_id)
deployment_id = func.get("deployment")
if not deployment_id:
return json.dumps(
{
"success": True,
"message": "No active deployment",
"function_id": function_id,
"active_deployment": None,
},
indent=2,
)
deployment = await client.get_deployment(function_id, deployment_id)
return json.dumps(
{"success": True, "function_id": function_id, "active_deployment": deployment},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_executions(
client: AppwriteClient,
function_id: str,
queries: list[str] | None = None,
search: str | None = None,
) -> str:
"""List function executions."""
try:
result = await client.list_executions(
function_id=function_id, queries=queries, search=search
)
executions = result.get("executions", [])
response = {
"success": True,
"function_id": function_id,
"total": result.get("total", len(executions)),
"executions": executions,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_execution(client: AppwriteClient, function_id: str, execution_id: str) -> str:
"""Get execution by ID."""
try:
result = await client.get_execution(function_id, execution_id)
return json.dumps({"success": True, "execution": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def execute_function(
client: AppwriteClient,
function_id: str,
body: str | None = None,
async_execution: bool = False,
path: str | None = None,
method: str = "POST",
headers: dict[str, str] | None = None,
) -> str:
"""Execute function."""
try:
result = await client.create_execution(
function_id=function_id,
body=body,
async_execution=async_execution,
path=path,
method=method,
headers=headers,
)
response = {
"success": True,
"execution_id": result.get("$id"),
"status": result.get("status"),
"status_code": result.get("responseStatusCode"),
"duration": result.get("duration"),
"response_body": result.get("responseBody"),
"logs": result.get("logs"),
"errors": result.get("errors"),
}
if async_execution:
response["message"] = "Function execution started asynchronously"
else:
response["message"] = "Function executed successfully"
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_execution(client: AppwriteClient, function_id: str, execution_id: str) -> str:
"""Delete execution."""
try:
await client.delete_execution(function_id, execution_id)
return json.dumps(
{"success": True, "message": f"Execution '{execution_id}' deleted successfully"},
indent=2,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,579 @@
"""
Messaging Handler - manages Appwrite messaging (Email, SMS, Push notifications)
Phase I.4: 12 tools
- Topics: 4 (list, get, create, delete)
- Subscribers: 2 (create, delete)
- Messages: 6 (list, get, send_email, send_sms, send_push, delete)
"""
import json
from typing import Any
from plugins.appwrite.client import AppwriteClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (12 tools)"""
return [
# =====================
# TOPICS (4)
# =====================
{
"name": "list_topics",
"method_name": "list_topics",
"description": "List all messaging topics. Topics are groups for sending messages to multiple subscribers.",
"schema": {
"type": "object",
"properties": {
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_topic",
"method_name": "get_topic",
"description": "Get topic details by ID.",
"schema": {
"type": "object",
"properties": {"topic_id": {"type": "string", "description": "Topic ID"}},
"required": ["topic_id"],
},
"scope": "read",
},
{
"name": "create_topic",
"method_name": "create_topic",
"description": "Create a new messaging topic.",
"schema": {
"type": "object",
"properties": {
"topic_id": {
"type": "string",
"description": "Unique topic ID. Use 'unique()' for auto-generation",
},
"name": {"type": "string", "description": "Topic name"},
"subscribe": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Permissions for who can subscribe (e.g., ['users', 'any'])",
},
},
"required": ["topic_id", "name"],
},
"scope": "write",
},
{
"name": "delete_topic",
"method_name": "delete_topic",
"description": "Delete a messaging topic.",
"schema": {
"type": "object",
"properties": {"topic_id": {"type": "string", "description": "Topic ID to delete"}},
"required": ["topic_id"],
},
"scope": "write",
},
# =====================
# SUBSCRIBERS (2)
# =====================
{
"name": "create_subscriber",
"method_name": "create_subscriber",
"description": "Add a subscriber to a topic.",
"schema": {
"type": "object",
"properties": {
"topic_id": {"type": "string", "description": "Topic ID"},
"subscriber_id": {
"type": "string",
"description": "Unique subscriber ID. Use 'unique()' for auto-generation",
},
"target_id": {
"type": "string",
"description": "Target ID (user's target for messaging)",
},
},
"required": ["topic_id", "subscriber_id", "target_id"],
},
"scope": "write",
},
{
"name": "delete_subscriber",
"method_name": "delete_subscriber",
"description": "Remove a subscriber from a topic.",
"schema": {
"type": "object",
"properties": {
"topic_id": {"type": "string", "description": "Topic ID"},
"subscriber_id": {"type": "string", "description": "Subscriber ID to remove"},
},
"required": ["topic_id", "subscriber_id"],
},
"scope": "write",
},
# =====================
# MESSAGES (6)
# =====================
{
"name": "list_messages",
"method_name": "list_messages",
"description": "List all messages (sent and draft).",
"schema": {
"type": "object",
"properties": {
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_message",
"method_name": "get_message",
"description": "Get message details by ID.",
"schema": {
"type": "object",
"properties": {"message_id": {"type": "string", "description": "Message ID"}},
"required": ["message_id"],
},
"scope": "read",
},
{
"name": "send_email",
"method_name": "send_email",
"description": "Send an email message. Requires configured email provider.",
"schema": {
"type": "object",
"properties": {
"message_id": {
"type": "string",
"description": "Unique message ID. Use 'unique()' for auto-generation",
},
"subject": {"type": "string", "description": "Email subject"},
"content": {
"type": "string",
"description": "Email content (HTML supported if html=true)",
},
"topics": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Topic IDs to send to",
},
"users": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "User IDs to send to",
},
"targets": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Target IDs to send to",
},
"cc": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "CC recipients",
},
"bcc": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "BCC recipients",
},
"html": {
"type": "boolean",
"description": "Send as HTML email",
"default": True,
},
"draft": {
"type": "boolean",
"description": "Save as draft instead of sending",
"default": False,
},
"scheduled_at": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Schedule send time (ISO 8601 format)",
},
},
"required": ["message_id", "subject", "content"],
},
"scope": "write",
},
{
"name": "send_sms",
"method_name": "send_sms",
"description": "Send an SMS message. Requires configured SMS provider.",
"schema": {
"type": "object",
"properties": {
"message_id": {
"type": "string",
"description": "Unique message ID. Use 'unique()' for auto-generation",
},
"content": {"type": "string", "description": "SMS content"},
"topics": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Topic IDs to send to",
},
"users": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "User IDs to send to",
},
"targets": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Target IDs to send to",
},
"draft": {"type": "boolean", "description": "Save as draft", "default": False},
"scheduled_at": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Schedule send time (ISO 8601 format)",
},
},
"required": ["message_id", "content"],
},
"scope": "write",
},
{
"name": "send_push",
"method_name": "send_push",
"description": "Send a push notification. Requires configured push provider (FCM/APNs).",
"schema": {
"type": "object",
"properties": {
"message_id": {
"type": "string",
"description": "Unique message ID. Use 'unique()' for auto-generation",
},
"title": {"type": "string", "description": "Notification title"},
"body": {"type": "string", "description": "Notification body"},
"topics": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Topic IDs to send to",
},
"users": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "User IDs to send to",
},
"targets": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Target IDs to send to",
},
"data": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Custom data payload",
},
"action": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Action URL",
},
"image": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Image URL",
},
"icon": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Icon URL",
},
"sound": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Sound name",
},
"badge": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Badge number (iOS)",
},
"draft": {"type": "boolean", "description": "Save as draft", "default": False},
"scheduled_at": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Schedule send time (ISO 8601 format)",
},
},
"required": ["message_id", "title", "body"],
},
"scope": "write",
},
{
"name": "delete_message",
"method_name": "delete_message",
"description": "Delete a message.",
"schema": {
"type": "object",
"properties": {
"message_id": {"type": "string", "description": "Message ID to delete"}
},
"required": ["message_id"],
},
"scope": "write",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_topics(
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
) -> str:
"""List all topics."""
try:
result = await client.list_topics(queries=queries, search=search)
topics = result.get("topics", [])
response = {"success": True, "total": result.get("total", len(topics)), "topics": topics}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_topic(client: AppwriteClient, topic_id: str) -> str:
"""Get topic by ID."""
try:
result = await client.get_topic(topic_id)
return json.dumps({"success": True, "topic": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_topic(
client: AppwriteClient, topic_id: str, name: str, subscribe: list[str] | None = None
) -> str:
"""Create a new topic."""
try:
result = await client.create_topic(topic_id=topic_id, name=name, subscribe=subscribe)
return json.dumps(
{"success": True, "message": f"Topic '{name}' created successfully", "topic": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_topic(client: AppwriteClient, topic_id: str) -> str:
"""Delete topic."""
try:
await client.delete_topic(topic_id)
return json.dumps(
{"success": True, "message": f"Topic '{topic_id}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_subscriber(
client: AppwriteClient, topic_id: str, subscriber_id: str, target_id: str
) -> str:
"""Add subscriber to topic."""
try:
result = await client.create_subscriber(
topic_id=topic_id, subscriber_id=subscriber_id, target_id=target_id
)
return json.dumps(
{"success": True, "message": "Subscriber added to topic", "subscriber": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_subscriber(client: AppwriteClient, topic_id: str, subscriber_id: str) -> str:
"""Remove subscriber from topic."""
try:
await client.delete_subscriber(topic_id, subscriber_id)
return json.dumps(
{"success": True, "message": f"Subscriber '{subscriber_id}' removed from topic"},
indent=2,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_messages(
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
) -> str:
"""List all messages."""
try:
result = await client.list_messages(queries=queries, search=search)
messages = result.get("messages", [])
response = {
"success": True,
"total": result.get("total", len(messages)),
"messages": messages,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_message(client: AppwriteClient, message_id: str) -> str:
"""Get message by ID."""
try:
result = await client.get_message(message_id)
return json.dumps({"success": True, "message": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def send_email(
client: AppwriteClient,
message_id: str,
subject: str,
content: str,
topics: list[str] | None = None,
users: list[str] | None = None,
targets: list[str] | None = None,
cc: list[str] | None = None,
bcc: list[str] | None = None,
html: bool = True,
draft: bool = False,
scheduled_at: str | None = None,
) -> str:
"""Send email message."""
try:
result = await client.create_email(
message_id=message_id,
subject=subject,
content=content,
topics=topics,
users=users,
targets=targets,
cc=cc,
bcc=bcc,
html=html,
draft=draft,
scheduled_at=scheduled_at,
)
action = "saved as draft" if draft else "sent"
if scheduled_at:
action = f"scheduled for {scheduled_at}"
return json.dumps(
{"success": True, "message": f"Email {action} successfully", "email": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
error_msg = str(e)
if "provider" in error_msg.lower():
error_msg += " (Hint: Make sure you have configured an email provider in Appwrite)"
return json.dumps({"success": False, "error": error_msg}, indent=2)
async def send_sms(
client: AppwriteClient,
message_id: str,
content: str,
topics: list[str] | None = None,
users: list[str] | None = None,
targets: list[str] | None = None,
draft: bool = False,
scheduled_at: str | None = None,
) -> str:
"""Send SMS message."""
try:
result = await client.create_sms(
message_id=message_id,
content=content,
topics=topics,
users=users,
targets=targets,
draft=draft,
scheduled_at=scheduled_at,
)
action = "saved as draft" if draft else "sent"
if scheduled_at:
action = f"scheduled for {scheduled_at}"
return json.dumps(
{"success": True, "message": f"SMS {action} successfully", "sms": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
error_msg = str(e)
if "provider" in error_msg.lower():
error_msg += " (Hint: Make sure you have configured an SMS provider in Appwrite)"
return json.dumps({"success": False, "error": error_msg}, indent=2)
async def send_push(
client: AppwriteClient,
message_id: str,
title: str,
body: str,
topics: list[str] | None = None,
users: list[str] | None = None,
targets: list[str] | None = None,
data: dict[str, Any] | None = None,
action: str | None = None,
image: str | None = None,
icon: str | None = None,
sound: str | None = None,
badge: int | None = None,
draft: bool = False,
scheduled_at: str | None = None,
) -> str:
"""Send push notification."""
try:
result = await client.create_push(
message_id=message_id,
title=title,
body=body,
topics=topics,
users=users,
targets=targets,
data_payload=data,
action=action,
image=image,
icon=icon,
sound=sound,
badge=badge,
draft=draft,
scheduled_at=scheduled_at,
)
action_text = "saved as draft" if draft else "sent"
if scheduled_at:
action_text = f"scheduled for {scheduled_at}"
return json.dumps(
{
"success": True,
"message": f"Push notification {action_text} successfully",
"push": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
error_msg = str(e)
if "provider" in error_msg.lower():
error_msg += (
" (Hint: Make sure you have configured a push provider (FCM/APNs) in Appwrite)"
)
return json.dumps({"success": False, "error": error_msg}, indent=2)
async def delete_message(client: AppwriteClient, message_id: str) -> str:
"""Delete message."""
try:
await client.delete_message(message_id)
return json.dumps(
{"success": True, "message": f"Message '{message_id}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,685 @@
"""
Storage Handler - manages Appwrite storage buckets and files
Phase I.3: 14 tools
- Buckets: 5 (list, get, create, update, delete)
- Files: 9 (list, get, create, update, delete, download, preview, view, get_url)
"""
import json
from typing import Any
from plugins.appwrite.client import AppwriteClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (14 tools)"""
return [
# =====================
# BUCKETS (5)
# =====================
{
"name": "list_buckets",
"method_name": "list_buckets",
"description": "List all storage buckets.",
"schema": {
"type": "object",
"properties": {
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term to filter buckets",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_bucket",
"method_name": "get_bucket",
"description": "Get bucket details by ID.",
"schema": {
"type": "object",
"properties": {"bucket_id": {"type": "string", "description": "Bucket ID"}},
"required": ["bucket_id"],
},
"scope": "read",
},
{
"name": "create_bucket",
"method_name": "create_bucket",
"description": "Create a new storage bucket.",
"schema": {
"type": "object",
"properties": {
"bucket_id": {
"type": "string",
"description": "Unique bucket ID. Use 'unique()' for auto-generation",
},
"name": {"type": "string", "description": "Bucket name"},
"permissions": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Bucket permissions",
},
"file_security": {
"type": "boolean",
"description": "Enable file-level security",
"default": True,
},
"enabled": {"type": "boolean", "description": "Enable bucket", "default": True},
"maximum_file_size": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Maximum file size in bytes",
},
"allowed_file_extensions": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Allowed file extensions (e.g., ['jpg', 'png', 'pdf'])",
},
"compression": {
"type": "string",
"enum": ["none", "gzip", "zstd"],
"description": "Compression algorithm",
"default": "none",
},
"encryption": {
"type": "boolean",
"description": "Enable encryption",
"default": True,
},
"antivirus": {
"type": "boolean",
"description": "Enable antivirus scanning",
"default": True,
},
},
"required": ["bucket_id", "name"],
},
"scope": "write",
},
{
"name": "update_bucket",
"method_name": "update_bucket",
"description": "Update bucket settings.",
"schema": {
"type": "object",
"properties": {
"bucket_id": {"type": "string", "description": "Bucket ID"},
"name": {"type": "string", "description": "Bucket name"},
"permissions": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "New permissions",
},
"enabled": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Enable/disable bucket",
},
"maximum_file_size": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Maximum file size in bytes",
},
"allowed_file_extensions": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Allowed extensions",
},
},
"required": ["bucket_id", "name"],
},
"scope": "write",
},
{
"name": "delete_bucket",
"method_name": "delete_bucket",
"description": "Delete a storage bucket. All files in the bucket will be deleted.",
"schema": {
"type": "object",
"properties": {
"bucket_id": {"type": "string", "description": "Bucket ID to delete"}
},
"required": ["bucket_id"],
},
"scope": "admin",
},
# =====================
# FILES (9)
# =====================
{
"name": "list_files",
"method_name": "list_files",
"description": "List files in a bucket.",
"schema": {
"type": "object",
"properties": {
"bucket_id": {"type": "string", "description": "Bucket ID"},
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term to filter files by name",
},
},
"required": ["bucket_id"],
},
"scope": "read",
},
{
"name": "get_file",
"method_name": "get_file",
"description": "Get file metadata (not content).",
"schema": {
"type": "object",
"properties": {
"bucket_id": {"type": "string", "description": "Bucket ID"},
"file_id": {"type": "string", "description": "File ID"},
},
"required": ["bucket_id", "file_id"],
},
"scope": "read",
},
{
"name": "delete_file",
"method_name": "delete_file",
"description": "Delete a file from storage.",
"schema": {
"type": "object",
"properties": {
"bucket_id": {"type": "string", "description": "Bucket ID"},
"file_id": {"type": "string", "description": "File ID to delete"},
},
"required": ["bucket_id", "file_id"],
},
"scope": "write",
},
{
"name": "download_file",
"method_name": "download_file",
"description": "Download file content. Returns base64 encoded data.",
"schema": {
"type": "object",
"properties": {
"bucket_id": {"type": "string", "description": "Bucket ID"},
"file_id": {"type": "string", "description": "File ID"},
},
"required": ["bucket_id", "file_id"],
},
"scope": "read",
},
{
"name": "get_file_preview",
"method_name": "get_file_preview",
"description": "Get image preview with transformations (resize, crop, etc.). Only works for image files.",
"schema": {
"type": "object",
"properties": {
"bucket_id": {"type": "string", "description": "Bucket ID"},
"file_id": {"type": "string", "description": "File ID"},
"width": {
"anyOf": [
{"type": "integer", "minimum": 0, "maximum": 4000},
{"type": "null"},
],
"description": "Width in pixels (0-4000)",
},
"height": {
"anyOf": [
{"type": "integer", "minimum": 0, "maximum": 4000},
{"type": "null"},
],
"description": "Height in pixels (0-4000)",
},
"gravity": {
"anyOf": [
{
"type": "string",
"enum": [
"center",
"top",
"top-left",
"top-right",
"left",
"right",
"bottom",
"bottom-left",
"bottom-right",
],
},
{"type": "null"},
],
"description": "Crop gravity",
},
"quality": {
"anyOf": [
{"type": "integer", "minimum": 0, "maximum": 100},
{"type": "null"},
],
"description": "Image quality (0-100)",
},
"border_width": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Border width in pixels",
},
"border_color": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Border color (hex without #)",
},
"border_radius": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Border radius for rounded corners",
},
"rotation": {
"anyOf": [
{"type": "integer", "minimum": 0, "maximum": 360},
{"type": "null"},
],
"description": "Rotation angle (0-360)",
},
"background": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Background color (hex without #)",
},
"output": {
"anyOf": [
{
"type": "string",
"enum": ["jpeg", "jpg", "png", "gif", "webp", "avif"],
},
{"type": "null"},
],
"description": "Output format",
},
},
"required": ["bucket_id", "file_id"],
},
"scope": "read",
},
{
"name": "get_file_view",
"method_name": "get_file_view",
"description": "Get file content for viewing in browser. Returns base64 encoded data.",
"schema": {
"type": "object",
"properties": {
"bucket_id": {"type": "string", "description": "Bucket ID"},
"file_id": {"type": "string", "description": "File ID"},
},
"required": ["bucket_id", "file_id"],
},
"scope": "read",
},
{
"name": "get_file_url",
"method_name": "get_file_url",
"description": "Get the public URL for a file (if bucket is public).",
"schema": {
"type": "object",
"properties": {
"bucket_id": {"type": "string", "description": "Bucket ID"},
"file_id": {"type": "string", "description": "File ID"},
"url_type": {
"type": "string",
"enum": ["view", "download", "preview"],
"description": "Type of URL to generate",
"default": "view",
},
},
"required": ["bucket_id", "file_id"],
},
"scope": "read",
},
{
"name": "bulk_delete_files",
"method_name": "bulk_delete_files",
"description": "Delete multiple files from a bucket.",
"schema": {
"type": "object",
"properties": {
"bucket_id": {"type": "string", "description": "Bucket ID"},
"file_ids": {
"type": "array",
"items": {"type": "string"},
"description": "Array of file IDs to delete",
},
},
"required": ["bucket_id", "file_ids"],
},
"scope": "write",
},
{
"name": "get_bucket_stats",
"method_name": "get_bucket_stats",
"description": "Get storage statistics for a bucket (file count and total size).",
"schema": {
"type": "object",
"properties": {"bucket_id": {"type": "string", "description": "Bucket ID"}},
"required": ["bucket_id"],
},
"scope": "read",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_buckets(
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
) -> str:
"""List all buckets."""
try:
result = await client.list_buckets(queries=queries, search=search)
buckets = result.get("buckets", [])
response = {"success": True, "total": result.get("total", len(buckets)), "buckets": buckets}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_bucket(client: AppwriteClient, bucket_id: str) -> str:
"""Get bucket by ID."""
try:
result = await client.get_bucket(bucket_id)
return json.dumps({"success": True, "bucket": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_bucket(
client: AppwriteClient,
bucket_id: str,
name: str,
permissions: list[str] | None = None,
file_security: bool = True,
enabled: bool = True,
maximum_file_size: int | None = None,
allowed_file_extensions: list[str] | None = None,
compression: str = "none",
encryption: bool = True,
antivirus: bool = True,
) -> str:
"""Create a new bucket."""
try:
result = await client.create_bucket(
bucket_id=bucket_id,
name=name,
permissions=permissions,
file_security=file_security,
enabled=enabled,
maximum_file_size=maximum_file_size,
allowed_file_extensions=allowed_file_extensions,
compression=compression,
encryption=encryption,
antivirus=antivirus,
)
return json.dumps(
{"success": True, "message": f"Bucket '{name}' created successfully", "bucket": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_bucket(
client: AppwriteClient,
bucket_id: str,
name: str,
permissions: list[str] | None = None,
enabled: bool | None = None,
maximum_file_size: int | None = None,
allowed_file_extensions: list[str] | None = None,
) -> str:
"""Update bucket."""
try:
result = await client.update_bucket(
bucket_id=bucket_id,
name=name,
permissions=permissions,
enabled=enabled,
maximum_file_size=maximum_file_size,
allowed_file_extensions=allowed_file_extensions,
)
return json.dumps(
{"success": True, "message": "Bucket updated successfully", "bucket": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_bucket(client: AppwriteClient, bucket_id: str) -> str:
"""Delete bucket."""
try:
await client.delete_bucket(bucket_id)
return json.dumps(
{"success": True, "message": f"Bucket '{bucket_id}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_files(
client: AppwriteClient,
bucket_id: str,
queries: list[str] | None = None,
search: str | None = None,
) -> str:
"""List files in bucket."""
try:
result = await client.list_files(bucket_id=bucket_id, queries=queries, search=search)
files = result.get("files", [])
response = {
"success": True,
"bucket_id": bucket_id,
"total": result.get("total", len(files)),
"files": files,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_file(client: AppwriteClient, bucket_id: str, file_id: str) -> str:
"""Get file metadata."""
try:
result = await client.get_file(bucket_id, file_id)
return json.dumps({"success": True, "file": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_file(client: AppwriteClient, bucket_id: str, file_id: str) -> str:
"""Delete file."""
try:
await client.delete_file(bucket_id, file_id)
return json.dumps(
{"success": True, "message": f"File '{file_id}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def download_file(client: AppwriteClient, bucket_id: str, file_id: str) -> str:
"""Download file content."""
try:
result = await client.get_file_download(bucket_id, file_id)
return json.dumps(
{
"success": True,
"file_id": file_id,
"content_type": result.get("content_type"),
"size": result.get("size"),
"data_base64": 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_preview(
client: AppwriteClient,
bucket_id: str,
file_id: str,
width: int | None = None,
height: int | None = None,
gravity: str | None = None,
quality: int | None = None,
border_width: int | None = None,
border_color: str | None = None,
border_radius: int | None = None,
rotation: int | None = None,
background: str | None = None,
output: str | None = None,
) -> str:
"""Get file preview with transformations."""
try:
result = await client.get_file_preview(
bucket_id=bucket_id,
file_id=file_id,
width=width,
height=height,
gravity=gravity,
quality=quality,
border_width=border_width,
border_color=border_color,
border_radius=border_radius,
rotation=rotation,
background=background,
output=output,
)
return json.dumps(
{
"success": True,
"file_id": file_id,
"transformations": {
"width": width,
"height": height,
"quality": quality,
"output": output,
},
"content_type": result.get("content_type"),
"size": result.get("size"),
"data_base64": 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_view(client: AppwriteClient, bucket_id: str, file_id: str) -> str:
"""Get file for viewing."""
try:
result = await client.get_file_view(bucket_id, file_id)
return json.dumps(
{
"success": True,
"file_id": file_id,
"content_type": result.get("content_type"),
"size": result.get("size"),
"data_base64": 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: AppwriteClient, bucket_id: str, file_id: str, url_type: str = "view"
) -> str:
"""Get file URL."""
try:
base_url = client.base_url
if url_type == "download":
url = f"{base_url}/storage/buckets/{bucket_id}/files/{file_id}/download"
elif url_type == "preview":
url = f"{base_url}/storage/buckets/{bucket_id}/files/{file_id}/preview"
else:
url = f"{base_url}/storage/buckets/{bucket_id}/files/{file_id}/view"
return json.dumps(
{
"success": True,
"file_id": file_id,
"url_type": url_type,
"url": url,
"note": "URL requires authentication unless bucket is public",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def bulk_delete_files(client: AppwriteClient, bucket_id: str, file_ids: list[str]) -> str:
"""Delete multiple files."""
try:
results = []
errors = []
for file_id in file_ids:
try:
await client.delete_file(bucket_id, file_id)
results.append({"id": file_id, "success": True})
except Exception as e:
errors.append({"file_id": file_id, "error": str(e)})
response = {
"success": len(errors) == 0,
"deleted": len(results),
"failed": len(errors),
"results": results,
}
if errors:
response["errors"] = errors
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_bucket_stats(client: AppwriteClient, bucket_id: str) -> str:
"""Get bucket statistics."""
try:
# Get files without query params to avoid syntax errors
# Appwrite returns first 25 files by default
result = await client.list_files(bucket_id=bucket_id)
files = result.get("files", [])
total = result.get("total", len(files))
total_size = sum(f.get("sizeOriginal", 0) for f in files)
return json.dumps(
{
"success": True,
"bucket_id": bucket_id,
"stats": {
"file_count": total,
"total_size_bytes": total_size,
"total_size_mb": round(total_size / (1024 * 1024), 2),
"sample_count": len(files),
},
"note": (
f"Size calculated from {len(files)} sampled files"
if total > len(files)
else None
),
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,298 @@
"""
System Handler - manages Appwrite health checks and system utilities
Phase I.1: 8 tools
- Health: 6 (health_check, health_db, health_cache, health_storage, health_queue, health_time)
- Avatars: 2 (get_avatar_initials, get_qr_code)
"""
import json
from typing import Any
from plugins.appwrite.client import AppwriteClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (8 tools)"""
return [
# =====================
# HEALTH (6)
# =====================
{
"name": "health_check",
"method_name": "health_check",
"description": "Comprehensive health check of all Appwrite services. Returns status of database, cache, storage, and other services.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "health_db",
"method_name": "health_db",
"description": "Check database health status. Returns ping time and connection status.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "health_cache",
"method_name": "health_cache",
"description": "Check cache (Redis) health status.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "health_storage",
"method_name": "health_storage",
"description": "Check storage health status. Verifies local and S3 storage availability.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "health_queue",
"method_name": "health_queue",
"description": "Check queue health status. Returns queue sizes and processing status.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
{
"name": "health_time",
"method_name": "health_time",
"description": "Check time synchronization status. Important for security and token validation.",
"schema": {"type": "object", "properties": {}, "required": []},
"scope": "read",
},
# =====================
# AVATARS (2)
# =====================
{
"name": "get_avatar_initials",
"method_name": "get_avatar_initials",
"description": "Generate an avatar image from name initials. Returns base64 encoded image.",
"schema": {
"type": "object",
"properties": {
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Full name to generate initials from",
},
"width": {
"type": "integer",
"description": "Image width in pixels",
"default": 100,
"minimum": 1,
"maximum": 2000,
},
"height": {
"type": "integer",
"description": "Image height in pixels",
"default": 100,
"minimum": 1,
"maximum": 2000,
},
"background": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Background color in hex (without #)",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_qr_code",
"method_name": "get_qr_code",
"description": "Generate a QR code image for any text or URL. Returns base64 encoded PNG.",
"schema": {
"type": "object",
"properties": {
"text": {"type": "string", "description": "Text or URL to encode in QR code"},
"size": {
"type": "integer",
"description": "QR code size in pixels (width = height)",
"default": 400,
"minimum": 100,
"maximum": 1000,
},
"margin": {
"type": "integer",
"description": "Margin around QR code in modules",
"default": 1,
"minimum": 0,
"maximum": 10,
},
},
"required": ["text"],
},
"scope": "read",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def health_check(client: AppwriteClient) -> str:
"""Comprehensive health check of all services."""
try:
result = await client.health_check()
response = {
"success": True,
"healthy": result.get("healthy", False),
"services": result.get("services", {}),
"message": (
"All services operational" if result.get("healthy") else "Some services have issues"
),
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "healthy": False, "error": str(e)}, indent=2)
async def health_db(client: AppwriteClient) -> str:
"""Check database health."""
try:
result = await client.health_db()
response = {
"success": True,
"service": "database",
"status": result.get("status", "unknown"),
"ping": result.get("ping"),
"message": (
"Database is healthy" if result.get("status") == "pass" else "Database has issues"
),
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps(
{"success": False, "service": "database", "status": "error", "error": str(e)}, indent=2
)
async def health_cache(client: AppwriteClient) -> str:
"""Check cache health."""
try:
result = await client.health_cache()
response = {
"success": True,
"service": "cache",
"status": result.get("status", "unknown"),
"ping": result.get("ping"),
"message": "Cache is healthy" if result.get("status") == "pass" else "Cache has issues",
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps(
{"success": False, "service": "cache", "status": "error", "error": str(e)}, indent=2
)
async def health_storage(client: AppwriteClient) -> str:
"""Check storage health."""
try:
result = await client.health_storage_local()
response = {
"success": True,
"service": "storage",
"status": result.get("status", "unknown"),
"ping": result.get("ping"),
"message": (
"Storage is healthy" if result.get("status") == "pass" else "Storage has issues"
),
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps(
{"success": False, "service": "storage", "status": "error", "error": str(e)}, indent=2
)
async def health_queue(client: AppwriteClient) -> str:
"""Check queue health."""
try:
result = await client.health_queue()
response = {
"success": True,
"service": "queue",
"status": result.get("status", "unknown"),
"size": result.get("size"),
"message": "Queue is healthy" if result.get("status") == "pass" else "Queue has issues",
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps(
{"success": False, "service": "queue", "status": "error", "error": str(e)}, indent=2
)
async def health_time(client: AppwriteClient) -> str:
"""Check time synchronization."""
try:
result = await client.health_time()
response = {
"success": True,
"service": "time",
"local_time": result.get("localTime"),
"remote_time": result.get("remoteTime"),
"diff": result.get("diff"),
"message": (
"Time is synchronized"
if abs(result.get("diff", 999)) < 60
else "Time drift detected"
),
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps(
{"success": False, "service": "time", "status": "error", "error": str(e)}, indent=2
)
async def get_avatar_initials(
client: AppwriteClient,
name: str | None = None,
width: int = 100,
height: int = 100,
background: str | None = None,
) -> str:
"""Generate avatar from initials."""
try:
result = await client.get_avatar_initials(
name=name, width=width, height=height, background=background
)
response = {
"success": True,
"message": f"Avatar generated for '{name or 'Anonymous'}'",
"width": width,
"height": height,
"content_type": result.get("content_type", "image/png"),
"size": result.get("size"),
"data_base64": result.get("data"),
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_qr_code(client: AppwriteClient, text: str, size: int = 400, margin: int = 1) -> str:
"""Generate QR code."""
try:
result = await client.get_qr_code(text=text, size=size, margin=margin)
response = {
"success": True,
"message": "QR code generated successfully",
"text": text,
"size": size,
"content_type": result.get("content_type", "image/png"),
"data_size": result.get("size"),
"data_base64": result.get("data"),
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,374 @@
"""
Teams Handler - manages Appwrite teams and memberships
Phase I.2: 10 tools
- Teams: 5 (list, get, create, update, delete)
- Memberships: 5 (list_memberships, create_membership, update_membership, delete_membership, get_membership_status)
"""
import json
from typing import Any
from plugins.appwrite.client import AppwriteClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (10 tools)"""
return [
# =====================
# TEAMS (5)
# =====================
{
"name": "list_teams",
"method_name": "list_teams",
"description": "List all teams in the project.",
"schema": {
"type": "object",
"properties": {
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term to filter teams by name",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_team",
"method_name": "get_team",
"description": "Get team details by ID.",
"schema": {
"type": "object",
"properties": {"team_id": {"type": "string", "description": "Team ID"}},
"required": ["team_id"],
},
"scope": "read",
},
{
"name": "create_team",
"method_name": "create_team",
"description": "Create a new team. Use 'unique()' for auto-generated team ID.",
"schema": {
"type": "object",
"properties": {
"team_id": {
"type": "string",
"description": "Unique team ID. Use 'unique()' for auto-generation",
},
"name": {"type": "string", "description": "Team name"},
"roles": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Array of roles available in this team (e.g., ['owner', 'editor', 'viewer'])",
},
},
"required": ["team_id", "name"],
},
"scope": "write",
},
{
"name": "update_team",
"method_name": "update_team",
"description": "Update team name.",
"schema": {
"type": "object",
"properties": {
"team_id": {"type": "string", "description": "Team ID"},
"name": {"type": "string", "description": "New team name"},
},
"required": ["team_id", "name"],
},
"scope": "write",
},
{
"name": "delete_team",
"method_name": "delete_team",
"description": "Delete a team and all its memberships. This action is irreversible.",
"schema": {
"type": "object",
"properties": {"team_id": {"type": "string", "description": "Team ID to delete"}},
"required": ["team_id"],
},
"scope": "admin",
},
# =====================
# MEMBERSHIPS (5)
# =====================
{
"name": "list_team_memberships",
"method_name": "list_team_memberships",
"description": "List all memberships (members) of a team.",
"schema": {
"type": "object",
"properties": {
"team_id": {"type": "string", "description": "Team ID"},
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings for filtering",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term to filter members",
},
},
"required": ["team_id"],
},
"scope": "read",
},
{
"name": "create_team_membership",
"method_name": "create_team_membership",
"description": "Invite a user to join a team. Can invite by email, phone, or existing user ID.",
"schema": {
"type": "object",
"properties": {
"team_id": {"type": "string", "description": "Team ID"},
"roles": {
"type": "array",
"items": {"type": "string"},
"description": "Roles to assign to the member",
},
"email": {
"anyOf": [{"type": "string", "format": "email"}, {"type": "null"}],
"description": "Email address to invite",
},
"user_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Existing user ID to add",
},
"phone": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Phone number to invite",
},
"url": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Redirect URL after accepting invitation",
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Name of the invitee",
},
},
"required": ["team_id", "roles"],
},
"scope": "write",
},
{
"name": "update_membership",
"method_name": "update_membership",
"description": "Update team membership roles.",
"schema": {
"type": "object",
"properties": {
"team_id": {"type": "string", "description": "Team ID"},
"membership_id": {"type": "string", "description": "Membership ID"},
"roles": {
"type": "array",
"items": {"type": "string"},
"description": "New roles for the member",
},
},
"required": ["team_id", "membership_id", "roles"],
},
"scope": "write",
},
{
"name": "delete_membership",
"method_name": "delete_membership",
"description": "Remove a member from a team.",
"schema": {
"type": "object",
"properties": {
"team_id": {"type": "string", "description": "Team ID"},
"membership_id": {"type": "string", "description": "Membership ID to remove"},
},
"required": ["team_id", "membership_id"],
},
"scope": "write",
},
{
"name": "get_team_prefs",
"method_name": "get_team_prefs",
"description": "Get team preferences/settings.",
"schema": {
"type": "object",
"properties": {"team_id": {"type": "string", "description": "Team ID"}},
"required": ["team_id"],
},
"scope": "read",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_teams(
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
) -> str:
"""List all teams."""
try:
result = await client.list_teams(queries=queries, search=search)
teams = result.get("teams", [])
response = {"success": True, "total": result.get("total", len(teams)), "teams": teams}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_team(client: AppwriteClient, team_id: str) -> str:
"""Get team by ID."""
try:
result = await client.get_team(team_id)
return json.dumps({"success": True, "team": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_team(
client: AppwriteClient, team_id: str, name: str, roles: list[str] | None = None
) -> str:
"""Create a new team."""
try:
result = await client.create_team(team_id=team_id, name=name, roles=roles)
return json.dumps(
{"success": True, "message": f"Team '{name}' created successfully", "team": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_team(client: AppwriteClient, team_id: str, name: str) -> str:
"""Update team name."""
try:
result = await client.update_team(team_id=team_id, name=name)
return json.dumps(
{"success": True, "message": "Team updated successfully", "team": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_team(client: AppwriteClient, team_id: str) -> str:
"""Delete team."""
try:
await client.delete_team(team_id)
return json.dumps(
{"success": True, "message": f"Team '{team_id}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_team_memberships(
client: AppwriteClient,
team_id: str,
queries: list[str] | None = None,
search: str | None = None,
) -> str:
"""List team memberships."""
try:
result = await client.list_team_memberships(team_id=team_id, queries=queries, search=search)
memberships = result.get("memberships", [])
response = {
"success": True,
"team_id": team_id,
"total": result.get("total", len(memberships)),
"memberships": memberships,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_team_membership(
client: AppwriteClient,
team_id: str,
roles: list[str],
email: str | None = None,
user_id: str | None = None,
phone: str | None = None,
url: str | None = None,
name: str | None = None,
) -> str:
"""Create team membership (invite)."""
try:
result = await client.create_team_membership(
team_id=team_id,
roles=roles,
email=email,
user_id=user_id,
phone=phone,
url=url,
name=name,
)
target = email or user_id or phone or "user"
return json.dumps(
{
"success": True,
"message": f"Membership created/invitation sent to {target}",
"membership": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_membership(
client: AppwriteClient, team_id: str, membership_id: str, roles: list[str]
) -> str:
"""Update membership roles."""
try:
result = await client.update_membership(
team_id=team_id, membership_id=membership_id, roles=roles
)
return json.dumps(
{
"success": True,
"message": f"Membership roles updated to: {roles}",
"membership": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_membership(client: AppwriteClient, team_id: str, membership_id: str) -> str:
"""Delete membership."""
try:
await client.delete_membership(team_id, membership_id)
return json.dumps(
{"success": True, "message": f"Membership '{membership_id}' removed from team"},
indent=2,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_team_prefs(client: AppwriteClient, team_id: str) -> str:
"""Get team preferences (placeholder - requires team prefs endpoint)."""
try:
# Note: Team prefs endpoint may vary by Appwrite version
# This is a simplified version that returns team info
result = await client.get_team(team_id)
return json.dumps(
{"success": True, "team_id": team_id, "prefs": result.get("prefs", {}), "team": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

View File

@@ -0,0 +1,384 @@
"""
Users Handler - manages Appwrite user operations
Phase I.2: 12 tools
- User CRUD: 5 (list, get, create, update, delete)
- User Properties: 4 (update_email, update_phone, update_status, update_labels)
- Sessions: 3 (list_sessions, delete_sessions, delete_session)
"""
import json
from typing import Any
from plugins.appwrite.client import AppwriteClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (12 tools)"""
return [
# =====================
# USER CRUD (5)
# =====================
{
"name": "list_users",
"method_name": "list_users",
"description": "List all users in the project with optional filtering and search.",
"schema": {
"type": "object",
"properties": {
"queries": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Query strings (e.g., 'limit(25)', 'offset(0)')",
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term to filter users by name or email",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_user",
"method_name": "get_user",
"description": "Get user details by ID.",
"schema": {
"type": "object",
"properties": {"user_id": {"type": "string", "description": "User ID"}},
"required": ["user_id"],
},
"scope": "read",
},
{
"name": "create_user",
"method_name": "create_user",
"description": "Create a new user. Use 'unique()' for auto-generated user ID.",
"schema": {
"type": "object",
"properties": {
"user_id": {
"type": "string",
"description": "Unique user ID. Use 'unique()' for auto-generation",
},
"email": {
"anyOf": [{"type": "string", "format": "email"}, {"type": "null"}],
"description": "User email address",
},
"phone": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User phone number (E.164 format)",
},
"password": {
"anyOf": [{"type": "string", "minLength": 8}, {"type": "null"}],
"description": "User password (min 8 characters)",
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User display name",
},
},
"required": ["user_id"],
},
"scope": "write",
},
{
"name": "update_user_name",
"method_name": "update_user_name",
"description": "Update user display name.",
"schema": {
"type": "object",
"properties": {
"user_id": {"type": "string", "description": "User ID"},
"name": {"type": "string", "description": "New display name"},
},
"required": ["user_id", "name"],
},
"scope": "write",
},
{
"name": "delete_user",
"method_name": "delete_user",
"description": "Delete a user. This action is irreversible.",
"schema": {
"type": "object",
"properties": {"user_id": {"type": "string", "description": "User ID to delete"}},
"required": ["user_id"],
},
"scope": "admin",
},
# =====================
# USER PROPERTIES (4)
# =====================
{
"name": "update_user_email",
"method_name": "update_user_email",
"description": "Update user email address.",
"schema": {
"type": "object",
"properties": {
"user_id": {"type": "string", "description": "User ID"},
"email": {
"type": "string",
"format": "email",
"description": "New email address",
},
},
"required": ["user_id", "email"],
},
"scope": "write",
},
{
"name": "update_user_phone",
"method_name": "update_user_phone",
"description": "Update user phone number.",
"schema": {
"type": "object",
"properties": {
"user_id": {"type": "string", "description": "User ID"},
"number": {
"type": "string",
"description": "New phone number (E.164 format, e.g., +14155552671)",
},
},
"required": ["user_id", "number"],
},
"scope": "write",
},
{
"name": "update_user_status",
"method_name": "update_user_status",
"description": "Enable or disable a user account.",
"schema": {
"type": "object",
"properties": {
"user_id": {"type": "string", "description": "User ID"},
"status": {
"type": "boolean",
"description": "True to enable, False to disable",
},
},
"required": ["user_id", "status"],
},
"scope": "admin",
},
{
"name": "update_user_labels",
"method_name": "update_user_labels",
"description": "Set user labels for permission management. Labels can be used in permission strings like 'label:admin'.",
"schema": {
"type": "object",
"properties": {
"user_id": {"type": "string", "description": "User ID"},
"labels": {
"type": "array",
"items": {"type": "string"},
"description": "Array of label strings (replaces existing labels)",
},
},
"required": ["user_id", "labels"],
},
"scope": "admin",
},
# =====================
# SESSIONS (3)
# =====================
{
"name": "list_user_sessions",
"method_name": "list_user_sessions",
"description": "List all active sessions for a user.",
"schema": {
"type": "object",
"properties": {"user_id": {"type": "string", "description": "User ID"}},
"required": ["user_id"],
},
"scope": "read",
},
{
"name": "delete_user_sessions",
"method_name": "delete_user_sessions",
"description": "Delete all sessions for a user (force logout everywhere).",
"schema": {
"type": "object",
"properties": {"user_id": {"type": "string", "description": "User ID"}},
"required": ["user_id"],
},
"scope": "admin",
},
{
"name": "delete_user_session",
"method_name": "delete_user_session",
"description": "Delete a specific user session.",
"schema": {
"type": "object",
"properties": {
"user_id": {"type": "string", "description": "User ID"},
"session_id": {"type": "string", "description": "Session ID to delete"},
},
"required": ["user_id", "session_id"],
},
"scope": "admin",
},
]
# =====================
# HANDLER FUNCTIONS
# =====================
async def list_users(
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
) -> str:
"""List all users."""
try:
result = await client.list_users(queries=queries, search=search)
users = result.get("users", [])
response = {"success": True, "total": result.get("total", len(users)), "users": users}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def get_user(client: AppwriteClient, user_id: str) -> str:
"""Get user by ID."""
try:
result = await client.get_user(user_id)
return json.dumps({"success": True, "user": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def create_user(
client: AppwriteClient,
user_id: str,
email: str | None = None,
phone: str | None = None,
password: str | None = None,
name: str | None = None,
) -> str:
"""Create a new user."""
try:
result = await client.create_user(
user_id=user_id, email=email, phone=phone, password=password, name=name
)
return json.dumps(
{"success": True, "message": "User created successfully", "user": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_user_name(client: AppwriteClient, user_id: str, name: str) -> str:
"""Update user name."""
try:
result = await client.update_user_name(user_id=user_id, name=name)
return json.dumps(
{"success": True, "message": "User name updated successfully", "user": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_user(client: AppwriteClient, user_id: str) -> str:
"""Delete user."""
try:
await client.delete_user(user_id)
return json.dumps(
{"success": True, "message": f"User '{user_id}' deleted successfully"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_user_email(client: AppwriteClient, user_id: str, email: str) -> str:
"""Update user email."""
try:
result = await client.update_user_email(user_id=user_id, email=email)
return json.dumps(
{"success": True, "message": "User email updated successfully", "user": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_user_phone(client: AppwriteClient, user_id: str, number: str) -> str:
"""Update user phone."""
try:
result = await client.update_user_phone(user_id=user_id, number=number)
return json.dumps(
{"success": True, "message": "User phone updated successfully", "user": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_user_status(client: AppwriteClient, user_id: str, status: bool) -> str:
"""Update user status (enable/disable)."""
try:
result = await client.update_user_status(user_id=user_id, status=status)
action = "enabled" if status else "disabled"
return json.dumps(
{"success": True, "message": f"User {action} successfully", "user": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def update_user_labels(client: AppwriteClient, user_id: str, labels: list[str]) -> str:
"""Update user labels."""
try:
result = await client.update_user_labels(user_id=user_id, labels=labels)
return json.dumps(
{"success": True, "message": f"User labels updated: {labels}", "user": result},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def list_user_sessions(client: AppwriteClient, user_id: str) -> str:
"""List user sessions."""
try:
result = await client.list_user_sessions(user_id)
sessions = result.get("sessions", [])
response = {
"success": True,
"user_id": user_id,
"total": result.get("total", len(sessions)),
"sessions": sessions,
}
return json.dumps(response, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_user_sessions(client: AppwriteClient, user_id: str) -> str:
"""Delete all user sessions."""
try:
await client.delete_user_sessions(user_id)
return json.dumps(
{"success": True, "message": f"All sessions for user '{user_id}' deleted"}, indent=2
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)
async def delete_user_session(client: AppwriteClient, user_id: str, session_id: str) -> str:
"""Delete a specific session."""
try:
await client.delete_user_session(user_id, session_id)
return json.dumps({"success": True, "message": f"Session '{session_id}' deleted"}, indent=2)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2)

166
plugins/appwrite/plugin.py Normal file
View File

@@ -0,0 +1,166 @@
"""
Appwrite Plugin - Self-Hosted Backend-as-a-Service Management
Complete Appwrite Self-Hosted management through REST APIs.
Provides tools for Databases, Documents, Users, Teams, Storage,
Functions, Messaging, and System operations.
For Self-Hosted instances deployed on Coolify.
"""
from typing import Any
from plugins.appwrite import handlers
from plugins.appwrite.client import AppwriteClient
from plugins.base import BasePlugin
class AppwritePlugin(BasePlugin):
"""
Appwrite Self-Hosted Plugin - Complete backend management.
Provides comprehensive Appwrite management capabilities including:
- Database operations (Databases, Collections, Attributes, Indexes)
- Document operations (CRUD, Bulk ops, Queries, Search)
- User management (CRUD, Labels, Sessions, Status)
- Team management (Teams, Memberships, Roles)
- Storage operations (Buckets, Files, Image transformation)
- Functions (Functions, Deployments, Executions)
- Messaging (Topics, Subscribers, Email/SMS/Push messages)
- System operations (Health checks, Avatars)
Phase I.1: Database (18) + Documents (12) + System (8) = 38 tools
Phase I.2: Users (12) + Teams (10) = 22 tools
Phase I.3: Storage (14) = 14 tools
Phase I.4: Functions (14) + Messaging (12) = 26 tools
Total: 100 tools - Complete!
"""
@staticmethod
def get_plugin_name() -> str:
"""Return plugin type identifier"""
return "appwrite"
@staticmethod
def get_required_config_keys() -> list[str]:
"""Return required configuration keys"""
return ["url", "project_id", "api_key"]
def __init__(self, config: dict[str, Any], project_id: str | None = None):
"""
Initialize Appwrite plugin with client.
Args:
config: Configuration dictionary containing:
- url: Appwrite instance URL (e.g., https://appwrite.example.com/v1)
- project_id: Appwrite project ID
- api_key: API key with appropriate scopes
project_id: Optional MCP project ID (auto-generated if not provided)
"""
super().__init__(config, project_id=project_id)
# Create Appwrite API client
self.client = AppwriteClient(
base_url=config["url"], project_id=config["project_id"], api_key=config["api_key"]
)
@staticmethod
def get_tool_specifications() -> list[dict[str, Any]]:
"""
Return all tool specifications for ToolGenerator.
This method is called by ToolGenerator to create unified tools
with site parameter routing.
Returns:
List of tool specification dictionaries
"""
specs = []
# Phase I.1: Core (38 tools)
specs.extend(handlers.databases.get_tool_specifications()) # 18 tools
specs.extend(handlers.documents.get_tool_specifications()) # 12 tools
specs.extend(handlers.system.get_tool_specifications()) # 8 tools
# Phase I.2: Auth & Teams (22 tools)
specs.extend(handlers.users.get_tool_specifications()) # 12 tools
specs.extend(handlers.teams.get_tool_specifications()) # 10 tools
# Phase I.3: Storage (14 tools)
specs.extend(handlers.storage.get_tool_specifications()) # 14 tools
# Phase I.4: Functions & Messaging (26 tools)
specs.extend(handlers.functions.get_tool_specifications()) # 14 tools
specs.extend(handlers.messaging.get_tool_specifications()) # 12 tools
return specs
def __getattr__(self, name: str):
"""
Dynamically delegate method calls to appropriate handlers.
This allows ToolGenerator to call methods like plugin.list_databases()
without explicitly defining each method.
Args:
name: Method name being called
Returns:
Handler function from the appropriate handler module
"""
# Try to find the method in handler modules
handler_modules = [
# Phase I.1: Core
handlers.databases,
handlers.documents,
handlers.system,
# Phase I.2: Auth & Teams
handlers.users,
handlers.teams,
# Phase I.3: Storage
handlers.storage,
# Phase I.4: Functions & Messaging
handlers.functions,
handlers.messaging,
]
for handler_module in handler_modules:
if hasattr(handler_module, name):
func = getattr(handler_module, name)
# Create wrapper that passes self.client
async def wrapper(_func=func, **kwargs):
return await _func(self.client, **kwargs)
return wrapper
# Method not found in any handler
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
async def check_health(self) -> dict[str, Any]:
"""
Check if Appwrite instance is accessible (internal use).
Note: This is named check_health to avoid shadowing the handler's
health_check function which is exposed as an MCP tool.
Returns:
Dict containing health check result
"""
try:
result = await self.client.health_check()
is_healthy = result.get("status") == "pass"
return {
"healthy": is_healthy,
"message": f"Appwrite instance at {self.client.base_url} is {'accessible' if is_healthy else 'not accessible'}",
}
except Exception as e:
return {"healthy": False, "message": f"Appwrite health check failed: {str(e)}"}
async def health_check(self, **kwargs) -> str:
"""
Override BasePlugin.health_check to use handler function.
This ensures the MCP tool returns a JSON string, not a Dict.
"""
return await handlers.system.health_check(self.client)