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:
484
plugins/supabase/handlers/storage.py
Normal file
484
plugins/supabase/handlers/storage.py
Normal file
@@ -0,0 +1,484 @@
|
||||
"""Storage Handler - manages Supabase Storage (buckets and files)"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.supabase.client import SupabaseClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator (12 tools)"""
|
||||
return [
|
||||
{
|
||||
"name": "list_buckets",
|
||||
"method_name": "list_buckets",
|
||||
"description": "List all storage buckets. Returns bucket names, public/private status, and settings.",
|
||||
"schema": {"type": "object", "properties": {}},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_bucket",
|
||||
"method_name": "get_bucket",
|
||||
"description": "Get detailed information about a specific bucket including settings and limits.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"bucket_id": {"type": "string", "description": "Bucket name/ID"}},
|
||||
"required": ["bucket_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_bucket",
|
||||
"method_name": "create_bucket",
|
||||
"description": "Create a new storage bucket. Can be public (accessible without auth) or private.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Bucket name (lowercase, no spaces)",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$",
|
||||
},
|
||||
"public": {
|
||||
"type": "boolean",
|
||||
"description": "Make bucket publicly accessible",
|
||||
"default": False,
|
||||
},
|
||||
"file_size_limit": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Max file size in bytes (e.g., 52428800 for 50MB)",
|
||||
},
|
||||
"allowed_mime_types": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Allowed MIME types (e.g., ['image/png', 'image/jpeg'])",
|
||||
},
|
||||
},
|
||||
"required": ["name"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "update_bucket",
|
||||
"method_name": "update_bucket",
|
||||
"description": "Update bucket settings like public access, file size limit, or allowed MIME types.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket name/ID"},
|
||||
"public": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Set public access",
|
||||
},
|
||||
"file_size_limit": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Max file size in bytes",
|
||||
},
|
||||
"allowed_mime_types": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Allowed MIME types",
|
||||
},
|
||||
},
|
||||
"required": ["bucket_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "delete_bucket",
|
||||
"method_name": "delete_bucket",
|
||||
"description": "Delete a storage bucket. Bucket must be empty first (use empty_bucket).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket name/ID to delete"}
|
||||
},
|
||||
"required": ["bucket_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "empty_bucket",
|
||||
"method_name": "empty_bucket",
|
||||
"description": "Delete all files in a bucket. Use before delete_bucket.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket name/ID to empty"}
|
||||
},
|
||||
"required": ["bucket_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "list_files",
|
||||
"method_name": "list_files",
|
||||
"description": "List files in a bucket or folder path. Returns file names, sizes, and metadata.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket": {"type": "string", "description": "Bucket name"},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Folder path prefix (empty for root)",
|
||||
"default": "",
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Max files to return",
|
||||
"default": 100,
|
||||
},
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"description": "Offset for pagination",
|
||||
"default": 0,
|
||||
},
|
||||
},
|
||||
"required": ["bucket"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "upload_file",
|
||||
"method_name": "upload_file",
|
||||
"description": "Upload a file to storage. Content should be base64 encoded.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket": {"type": "string", "description": "Bucket name"},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "File path including filename (e.g., 'users/123/avatar.png')",
|
||||
},
|
||||
"content_base64": {
|
||||
"type": "string",
|
||||
"description": "File content encoded in base64",
|
||||
},
|
||||
"content_type": {
|
||||
"type": "string",
|
||||
"description": "MIME type (e.g., 'image/png', 'application/pdf')",
|
||||
"default": "application/octet-stream",
|
||||
},
|
||||
"upsert": {
|
||||
"type": "boolean",
|
||||
"description": "Overwrite if file exists",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["bucket", "path", "content_base64"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "download_file",
|
||||
"method_name": "download_file",
|
||||
"description": "Download a file from storage. Returns base64 encoded content.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket": {"type": "string", "description": "Bucket name"},
|
||||
"path": {"type": "string", "description": "File path"},
|
||||
},
|
||||
"required": ["bucket", "path"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "delete_files",
|
||||
"method_name": "delete_files",
|
||||
"description": "Delete one or more files from a bucket.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket": {"type": "string", "description": "Bucket name"},
|
||||
"paths": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "List of file paths to delete",
|
||||
},
|
||||
},
|
||||
"required": ["bucket", "paths"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "move_file",
|
||||
"method_name": "move_file",
|
||||
"description": "Move or rename a file within the same bucket.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket": {"type": "string", "description": "Bucket name"},
|
||||
"from_path": {"type": "string", "description": "Current file path"},
|
||||
"to_path": {"type": "string", "description": "New file path"},
|
||||
},
|
||||
"required": ["bucket", "from_path", "to_path"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "get_public_url",
|
||||
"method_name": "get_public_url",
|
||||
"description": "Get the public URL for a file. Only works for files in public buckets.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket": {"type": "string", "description": "Bucket name (must be public)"},
|
||||
"path": {"type": "string", "description": "File path"},
|
||||
},
|
||||
"required": ["bucket", "path"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
# =====================
|
||||
# Storage Operations (12 tools)
|
||||
# =====================
|
||||
|
||||
async def list_buckets(client: SupabaseClient) -> str:
|
||||
"""List all storage buckets"""
|
||||
try:
|
||||
result = await client.list_buckets()
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"count": len(result) if isinstance(result, list) else 0,
|
||||
"buckets": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def get_bucket(client: SupabaseClient, bucket_id: str) -> str:
|
||||
"""Get bucket details"""
|
||||
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, ensure_ascii=False)
|
||||
|
||||
async def create_bucket(
|
||||
client: SupabaseClient,
|
||||
name: str,
|
||||
public: bool = False,
|
||||
file_size_limit: int | None = None,
|
||||
allowed_mime_types: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Create a new bucket"""
|
||||
try:
|
||||
result = await client.create_bucket(
|
||||
name=name,
|
||||
public=public,
|
||||
file_size_limit=file_size_limit,
|
||||
allowed_mime_types=allowed_mime_types,
|
||||
)
|
||||
|
||||
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, ensure_ascii=False)
|
||||
|
||||
async def update_bucket(
|
||||
client: SupabaseClient,
|
||||
bucket_id: str,
|
||||
public: bool | None = None,
|
||||
file_size_limit: int | None = None,
|
||||
allowed_mime_types: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Update bucket settings"""
|
||||
try:
|
||||
result = await client.update_bucket(
|
||||
bucket_id=bucket_id,
|
||||
public=public,
|
||||
file_size_limit=file_size_limit,
|
||||
allowed_mime_types=allowed_mime_types,
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Bucket '{bucket_id}' updated successfully",
|
||||
"bucket": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def delete_bucket(client: SupabaseClient, bucket_id: str) -> str:
|
||||
"""Delete a bucket"""
|
||||
try:
|
||||
await client.delete_bucket(bucket_id)
|
||||
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Bucket '{bucket_id}' deleted successfully"},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def empty_bucket(client: SupabaseClient, bucket_id: str) -> str:
|
||||
"""Empty a bucket (delete all files)"""
|
||||
try:
|
||||
await client.empty_bucket(bucket_id)
|
||||
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Bucket '{bucket_id}' emptied successfully"},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def list_files(
|
||||
client: SupabaseClient, bucket: str, path: str = "", limit: int = 100, offset: int = 0
|
||||
) -> str:
|
||||
"""List files in bucket/path"""
|
||||
try:
|
||||
result = await client.list_files(bucket=bucket, path=path, limit=limit, offset=offset)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"bucket": bucket,
|
||||
"path": path,
|
||||
"count": len(result) if isinstance(result, list) else 0,
|
||||
"files": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def upload_file(
|
||||
client: SupabaseClient,
|
||||
bucket: str,
|
||||
path: str,
|
||||
content_base64: str,
|
||||
content_type: str = "application/octet-stream",
|
||||
upsert: bool = False,
|
||||
) -> str:
|
||||
"""Upload a file"""
|
||||
try:
|
||||
# Decode base64 content
|
||||
try:
|
||||
content = base64.b64decode(content_base64)
|
||||
except Exception:
|
||||
return json.dumps(
|
||||
{"success": False, "error": "Invalid base64 content"}, indent=2, ensure_ascii=False
|
||||
)
|
||||
|
||||
result = await client.upload_file(
|
||||
bucket=bucket, path=path, content=content, content_type=content_type, upsert=upsert
|
||||
)
|
||||
|
||||
# Build public URL if bucket is public
|
||||
public_url = await client.get_public_url(bucket, path)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"File uploaded to {bucket}/{path}",
|
||||
"size": len(content),
|
||||
"content_type": content_type,
|
||||
"public_url": public_url,
|
||||
"result": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def download_file(client: SupabaseClient, bucket: str, path: str) -> str:
|
||||
"""Download a file (returns base64)"""
|
||||
try:
|
||||
result = await client.download_file(bucket, path)
|
||||
|
||||
if isinstance(result, dict) and "data" in result:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"bucket": bucket,
|
||||
"path": path,
|
||||
"content_type": result.get("content_type", "application/octet-stream"),
|
||||
"size": result.get("size", 0),
|
||||
"data_base64": result.get("data"),
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
else:
|
||||
return json.dumps(
|
||||
{"success": True, "bucket": bucket, "path": path, "result": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def delete_files(client: SupabaseClient, bucket: str, paths: list[str]) -> str:
|
||||
"""Delete files from bucket"""
|
||||
try:
|
||||
result = await client.delete_files(bucket, paths)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Deleted {len(paths)} file(s) from {bucket}",
|
||||
"deleted_paths": paths,
|
||||
"result": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def move_file(client: SupabaseClient, bucket: str, from_path: str, to_path: str) -> str:
|
||||
"""Move/rename a file"""
|
||||
try:
|
||||
result = await client.move_file(bucket, from_path, to_path)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"File moved from {from_path} to {to_path}",
|
||||
"bucket": bucket,
|
||||
"from_path": from_path,
|
||||
"to_path": to_path,
|
||||
"result": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
async def get_public_url(client: SupabaseClient, bucket: str, path: str) -> str:
|
||||
"""Get public URL for a file"""
|
||||
try:
|
||||
url = await client.get_public_url(bucket, path)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"bucket": bucket,
|
||||
"path": path,
|
||||
"public_url": url,
|
||||
"note": "URL only works if bucket is public",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
Reference in New Issue
Block a user