#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { Client, Databases, Users, Storage, Functions, Teams, Account, Health, Messaging, ID, Query, Permission, Role, IndexType } from "node-appwrite"; import { InputFile } from "node-appwrite/file"; import { readFileSync, existsSync } from "fs"; import { join } from "path"; import * as dotenv from "dotenv"; interface AppwriteConfig { projectId: string; apiEndpoint: string; apiKey: string; } class AppwriteMCPServer { private server: Server; private client: Client | null = null; private databases: Databases | null = null; private users: Users | null = null; private storage: Storage | null = null; private functions: Functions | null = null; private teams: Teams | null = null; private account: Account | null = null; private health: Health | null = null; private messaging: Messaging | null = null; private config: AppwriteConfig | null = null; constructor() { this.server = new Server( { name: "appwrite-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); this.loadConfig(); } private loadConfig(): void { // Load environment variables from .env file in current working directory dotenv.config({ path: join(process.cwd(), '.env') }); const projectId = process.env.APPWRITE_PROJECT_ID; const apiEndpoint = process.env.APPWRITE_API_ENDPOINT; const apiKey = process.env.APPWRITE_API_KEY; if (!projectId || !apiEndpoint || !apiKey) { console.error("Appwrite configuration missing. Please create a .env file with APPWRITE_PROJECT_ID, APPWRITE_API_ENDPOINT, and APPWRITE_API_KEY"); return; } this.config = { projectId, apiEndpoint, apiKey }; this.initializeAppwrite(); } private initializeAppwrite(): void { if (!this.config) return; this.client = new Client() .setEndpoint(this.config.apiEndpoint) .setProject(this.config.projectId) .setKey(this.config.apiKey); this.databases = new Databases(this.client); this.users = new Users(this.client); this.storage = new Storage(this.client); this.functions = new Functions(this.client); this.teams = new Teams(this.client); this.account = new Account(this.client); this.health = new Health(this.client); this.messaging = new Messaging(this.client); } private setupToolHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Database Operations { name: "create_database", description: "Create a new database in Appwrite", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "Unique ID for the database (optional)" }, name: { type: "string", description: "Name of the database" } }, required: ["name"] } }, { name: "list_databases", description: "List all databases in the Appwrite project", inputSchema: { type: "object", properties: {} } }, { name: "delete_database", description: "Delete a database from Appwrite", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database to delete" } }, required: ["databaseId"] } }, // Collection Operations { name: "create_collection", description: "Create a new collection in a database", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "Unique ID for the collection (optional)" }, name: { type: "string", description: "Name of the collection" }, permissions: { type: "array", description: "Collection permissions (optional)", items: { type: "string" } } }, required: ["databaseId", "name"] } }, { name: "list_collections", description: "List all collections in a database", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" } }, required: ["databaseId"] } }, { name: "update_collection", description: "Update collection name and permissions", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, name: { type: "string", description: "New collection name" }, permissions: { type: "array", description: "Collection permissions (optional)", items: { type: "string" } } }, required: ["databaseId", "collectionId", "name"] } }, { name: "delete_collection", description: "Delete a collection from a database", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection to delete" } }, required: ["databaseId", "collectionId"] } }, // Attribute Operations { name: "create_attribute", description: "Create any type of attribute in a collection (string, integer, float, boolean, datetime, email, IP, URL, enum, relationship)", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, key: { type: "string", description: "Attribute key" }, type: { type: "string", description: "Attribute type", enum: ["string", "integer", "float", "boolean", "datetime", "email", "ip", "url", "enum", "relationship"] }, required: { type: "boolean", description: "Is attribute required" }, default: { type: "string", description: "Default value (optional)" }, size: { type: "number", description: "Size for string/ip/url attributes (optional)" }, min: { type: "number", description: "Minimum value for integer/float attributes (optional)" }, max: { type: "number", description: "Maximum value for integer/float attributes (optional)" }, elements: { type: "array", items: { type: "string" }, description: "Array of allowed values for enum attributes (required for enum type)" }, relatedCollection: { type: "string", description: "Related collection ID for relationship attributes (required for relationship type)" }, relationType: { type: "string", description: "Relationship type (required for relationship type)", enum: ["oneToOne", "oneToMany", "manyToOne", "manyToMany"] }, twoWay: { type: "boolean", description: "Is relationship two-way (optional for relationship type)" }, twoWayKey: { type: "string", description: "Two-way relationship key (optional for relationship type)" } }, required: ["databaseId", "collectionId", "key", "type", "required"] } }, { name: "bulk_create_attributes", description: "Create multiple attributes at once in a collection", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, attributes: { type: "array", description: "Array of attribute definitions", items: { type: "object", properties: { key: { type: "string", description: "Attribute key" }, type: { type: "string", description: "Attribute type", enum: ["string", "integer", "float", "boolean", "datetime", "email", "ip", "url", "enum", "relationship"] }, required: { type: "boolean", description: "Is attribute required" }, default: { type: "string", description: "Default value (optional)" }, size: { type: "number", description: "Size for string/ip/url attributes (optional)" }, min: { type: "number", description: "Minimum value for integer/float attributes (optional)" }, max: { type: "number", description: "Maximum value for integer/float attributes (optional)" }, elements: { type: "array", items: { type: "string" }, description: "Array of allowed values for enum attributes (required for enum type)" }, relatedCollection: { type: "string", description: "Related collection ID for relationship attributes (required for relationship type)" }, relationType: { type: "string", description: "Relationship type (required for relationship type)", enum: ["oneToOne", "oneToMany", "manyToOne", "manyToMany"] }, twoWay: { type: "boolean", description: "Is relationship two-way (optional for relationship type)" }, twoWayKey: { type: "string", description: "Two-way relationship key (optional for relationship type)" } }, required: ["key", "type", "required"] } } }, required: ["databaseId", "collectionId", "attributes"] } }, { name: "bulk_delete_attributes", description: "Delete multiple attributes at once from a collection", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, attributeKeys: { type: "array", description: "Array of attribute keys to delete", items: { type: "string" } } }, required: ["databaseId", "collectionId", "attributeKeys"] } }, { name: "list_attributes", description: "List all attributes in a collection", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" } }, required: ["databaseId", "collectionId"] } }, { name: "delete_attribute", description: "Delete an attribute from a collection", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, key: { type: "string", description: "Attribute key to delete" } }, required: ["databaseId", "collectionId", "key"] } }, { name: "get_attribute", description: "Get details of a specific attribute", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, key: { type: "string", description: "Attribute key" } }, required: ["databaseId", "collectionId", "key"] } }, { name: "update_attribute", description: "Update any type of attribute (string, integer, float, boolean, datetime, email, IP, URL, enum, relationship)", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, key: { type: "string", description: "Attribute key" }, type: { type: "string", description: "Attribute type", enum: ["string", "integer", "float", "boolean", "datetime", "email", "ip", "url", "enum", "relationship"] }, required: { type: "boolean", description: "Is attribute required" }, default: { type: "string", description: "Default value (optional)" }, size: { type: "number", description: "Size for string/ip/url attributes (optional)" }, min: { type: "number", description: "Minimum value for integer/float attributes (optional)" }, max: { type: "number", description: "Maximum value for integer/float attributes (optional)" }, elements: { type: "array", items: { type: "string" }, description: "Array of allowed values for enum attributes (required for enum type)" } }, required: ["databaseId", "collectionId", "key", "type", "required"] } }, { name: "get_collection", description: "Get details of a specific collection", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" } }, required: ["databaseId", "collectionId"] } }, // Index Operations { name: "create_index", description: "Create an index in a collection", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, key: { type: "string", description: "Index key" }, type: { type: "string", description: "Index type (key, fulltext, unique)", enum: ["key", "fulltext", "unique"] }, attributes: { type: "array", description: "Array of attribute keys", items: { type: "string" } } }, required: ["databaseId", "collectionId", "key", "type", "attributes"] } }, { name: "list_indexes", description: "List all indexes in a collection", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" } }, required: ["databaseId", "collectionId"] } }, { name: "delete_index", description: "Delete an index from a collection", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, key: { type: "string", description: "Index key to delete" } }, required: ["databaseId", "collectionId", "key"] } }, { name: "get_index", description: "Get details of a specific index", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, key: { type: "string", description: "Index key" } }, required: ["databaseId", "collectionId", "key"] } }, // Document Operations { name: "create_document", description: "Create a new document in a collection", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, documentId: { type: "string", description: "Unique ID for the document (optional)" }, data: { type: "object", description: "Document data as JSON object" }, permissions: { type: "array", description: "Document permissions (optional)", items: { type: "string" } } }, required: ["databaseId", "collectionId", "data"] } }, { name: "get_document", description: "Get a document by ID", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, documentId: { type: "string", description: "ID of the document" } }, required: ["databaseId", "collectionId", "documentId"] } }, { name: "list_documents", description: "List documents in a collection with optional queries, limit, and offset", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } }, limit: { type: "number", description: "Maximum number of documents to return (default 25, max 5000)" }, offset: { type: "number", description: "Number of documents to skip for pagination (default 0)" } }, required: ["databaseId", "collectionId"] } }, { name: "update_document", description: "Update a document", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, documentId: { type: "string", description: "ID of the document" }, data: { type: "object", description: "Updated document data" }, permissions: { type: "array", description: "Document permissions (optional)", items: { type: "string" } } }, required: ["databaseId", "collectionId", "documentId", "data"] } }, { name: "delete_document", description: "Delete a document", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, documentId: { type: "string", description: "ID of the document" } }, required: ["databaseId", "collectionId", "documentId"] } }, // User Management Operations { name: "create_user", description: "Create a new user", inputSchema: { type: "object", properties: { userId: { type: "string", description: "Unique user ID (optional)" }, email: { type: "string", description: "User email" }, phone: { type: "string", description: "User phone number in international format (optional, e.g., '+1234567890')" }, password: { type: "string", description: "User password" }, name: { type: "string", description: "User name (optional)" } }, required: ["email", "password"] } }, { name: "list_users", description: "List all users with pagination", inputSchema: { type: "object", properties: { queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } }, limit: { type: "number", description: "Maximum number of users to return (default 25, max 5000)" }, offset: { type: "number", description: "Number of users to skip for pagination (default 0)" } } } }, { name: "get_user", description: "Get a user by ID", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID" } }, required: ["userId"] } }, { name: "update_user", description: "Update user email, name, and/or password", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID" }, email: { type: "string", description: "New email (optional)" }, name: { type: "string", description: "New name (optional)" }, password: { type: "string", description: "New password (optional)" } }, required: ["userId"] } }, { name: "delete_user", description: "Delete a user", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID" } }, required: ["userId"] } }, { name: "update_user_preferences", description: "Update user preferences", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID" }, prefs: { type: "object", description: "User preferences object" } }, required: ["userId", "prefs"] } }, // Storage Operations { name: "create_bucket", description: "Create a storage bucket", inputSchema: { type: "object", properties: { bucketId: { type: "string", description: "Unique bucket ID (optional)" }, name: { type: "string", description: "Bucket name" }, permissions: { type: "array", description: "Bucket permissions (optional)", items: { type: "string" } }, fileSecurity: { type: "boolean", description: "Enable file security (optional)" }, enabled: { type: "boolean", description: "Enable bucket (optional, default true)" } }, required: ["name"] } }, { name: "list_buckets", description: "List all storage buckets", inputSchema: { type: "object", properties: {} } }, { name: "get_bucket", description: "Get a bucket by ID", inputSchema: { type: "object", properties: { bucketId: { type: "string", description: "Bucket ID" } }, required: ["bucketId"] } }, { name: "update_bucket", description: "Update a storage bucket", inputSchema: { type: "object", properties: { bucketId: { type: "string", description: "Bucket ID" }, name: { type: "string", description: "Bucket name" }, permissions: { type: "array", description: "Bucket permissions (optional)", items: { type: "string" } }, fileSecurity: { type: "boolean", description: "Enable file security (optional)" }, enabled: { type: "boolean", description: "Enable bucket (optional)" } }, required: ["bucketId", "name"] } }, { name: "delete_bucket", description: "Delete a storage bucket", inputSchema: { type: "object", properties: { bucketId: { type: "string", description: "Bucket ID" } }, required: ["bucketId"] } }, { name: "list_files", description: "List files in a storage bucket", inputSchema: { type: "object", properties: { bucketId: { type: "string", description: "Bucket ID" } }, required: ["bucketId"] } }, { name: "get_file", description: "Get file details by ID", inputSchema: { type: "object", properties: { bucketId: { type: "string", description: "Bucket ID" }, fileId: { type: "string", description: "File ID" } }, required: ["bucketId", "fileId"] } }, { name: "get_file_url", description: "Get file URL (download, preview with transformations, or view)", inputSchema: { type: "object", properties: { bucketId: { type: "string", description: "Bucket ID" }, fileId: { type: "string", description: "File ID" }, type: { type: "string", enum: ["download", "preview", "view"], description: "URL type: download, preview, or view" }, width: { type: "number", description: "Preview width (optional, only for preview type)" }, height: { type: "number", description: "Preview height (optional, only for preview type)" }, gravity: { type: "string", description: "Image gravity (optional, only for preview type)" }, quality: { type: "number", description: "Image quality 0-100 (optional, only for preview type)" }, borderWidth: { type: "number", description: "Border width (optional, only for preview type)" }, borderColor: { type: "string", description: "Border color (optional, only for preview type)" }, borderRadius: { type: "number", description: "Border radius (optional, only for preview type)" }, opacity: { type: "number", description: "Opacity 0-1 (optional, only for preview type)" }, rotation: { type: "number", description: "Rotation in degrees (optional, only for preview type)" }, background: { type: "string", description: "Background color (optional, only for preview type)" }, output: { type: "string", description: "Output format (optional, only for preview type)" } }, required: ["bucketId", "fileId", "type"] } }, { name: "delete_file", description: "Delete a file", inputSchema: { type: "object", properties: { bucketId: { type: "string", description: "Bucket ID" }, fileId: { type: "string", description: "File ID" } }, required: ["bucketId", "fileId"] } }, { name: "create_file", description: "Upload a file to storage bucket", inputSchema: { type: "object", properties: { bucketId: { type: "string", description: "Bucket ID" }, fileId: { type: "string", description: "File ID (optional)" }, filePath: { type: "string", description: "Local file path to upload" }, permissions: { type: "array", description: "File permissions (optional)", items: { type: "string" } } }, required: ["bucketId", "filePath"] } }, { name: "update_file", description: "Update file metadata", inputSchema: { type: "object", properties: { bucketId: { type: "string", description: "Bucket ID" }, fileId: { type: "string", description: "File ID" }, name: { type: "string", description: "File name (optional)" }, permissions: { type: "array", description: "File permissions (optional)", items: { type: "string" } } }, required: ["bucketId", "fileId"] } }, // Function Operations { name: "create_function", description: "Create a new function", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Unique function ID (optional)" }, name: { type: "string", description: "Function name" }, runtime: { type: "string", description: "Function runtime (e.g., node-18.0, python-3.9)" }, execute: { type: "array", description: "Execute permissions (optional)", items: { type: "string" } }, events: { type: "array", description: "Events that trigger the function (optional)", items: { type: "string" } }, schedule: { type: "string", description: "CRON schedule (optional)" }, timeout: { type: "number", description: "Timeout in seconds (optional, max 900)" } }, required: ["name", "runtime"] } }, { name: "list_functions", description: "List all functions", inputSchema: { type: "object", properties: {} } }, { name: "get_function", description: "Get a function by ID", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" } }, required: ["functionId"] } }, { name: "update_function", description: "Update a function", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" }, name: { type: "string", description: "Function name" }, runtime: { type: "string", description: "Function runtime (optional)" }, execute: { type: "array", description: "Execute permissions (optional)", items: { type: "string" } }, events: { type: "array", description: "Events that trigger the function (optional)", items: { type: "string" } }, schedule: { type: "string", description: "CRON schedule (optional)" }, timeout: { type: "number", description: "Timeout in seconds (optional)" } }, required: ["functionId", "name"] } }, { name: "delete_function", description: "Delete a function", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" } }, required: ["functionId"] } }, { name: "list_function_deployments", description: "List function deployments", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" }, queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } }, search: { type: "string", description: "Search term (optional)" } }, required: ["functionId"] } }, { name: "delete_function_deployment", description: "Delete function deployment", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" }, deploymentId: { type: "string", description: "Deployment ID" } }, required: ["functionId", "deploymentId"] } }, { name: "create_function_variable", description: "Create function variable", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" }, key: { type: "string", description: "Variable key" }, value: { type: "string", description: "Variable value" }, secret: { type: "boolean", description: "Is secret variable (optional)" } }, required: ["functionId", "key", "value"] } }, { name: "list_function_variables", description: "List function variables", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" } }, required: ["functionId"] } }, { name: "get_function_variable", description: "Get function variable by ID", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" }, variableId: { type: "string", description: "Variable ID" } }, required: ["functionId", "variableId"] } }, { name: "update_function_variable", description: "Update function variable", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" }, variableId: { type: "string", description: "Variable ID" }, key: { type: "string", description: "Variable key" }, value: { type: "string", description: "Variable value (optional)" }, secret: { type: "boolean", description: "Is secret variable (optional)" } }, required: ["functionId", "variableId", "key"] } }, { name: "delete_function_variable", description: "Delete function variable", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" }, variableId: { type: "string", description: "Variable ID" } }, required: ["functionId", "variableId"] } }, // Team Operations { name: "create_team", description: "Create a new team", inputSchema: { type: "object", properties: { teamId: { type: "string", description: "Unique team ID (optional)" }, name: { type: "string", description: "Team name" }, roles: { type: "array", description: "Team roles (optional)", items: { type: "string" } } }, required: ["name"] } }, { name: "list_teams", description: "List all teams", inputSchema: { type: "object", properties: {} } }, { name: "get_team", description: "Get a team by ID", inputSchema: { type: "object", properties: { teamId: { type: "string", description: "Team ID" } }, required: ["teamId"] } }, { name: "update_team", description: "Update a team", inputSchema: { type: "object", properties: { teamId: { type: "string", description: "Team ID" }, name: { type: "string", description: "Team name" } }, required: ["teamId", "name"] } }, { name: "delete_team", description: "Delete a team", inputSchema: { type: "object", properties: { teamId: { type: "string", description: "Team ID" } }, required: ["teamId"] } }, // Messaging Operations { name: "create_messaging_provider", description: "Create a messaging provider", inputSchema: { type: "object", properties: { providerId: { type: "string", description: "Provider ID (optional)" }, name: { type: "string", description: "Provider name" }, type: { type: "string", description: "Provider type (email, sms, push)" }, enabled: { type: "boolean", description: "Enable provider (optional)" } }, required: ["name", "type"] } }, { name: "list_messaging_providers", description: "List messaging providers", inputSchema: { type: "object", properties: {} } }, { name: "get_messaging_provider", description: "Get messaging provider by ID", inputSchema: { type: "object", properties: { providerId: { type: "string", description: "Provider ID" } }, required: ["providerId"] } }, { name: "update_messaging_provider", description: "Update messaging provider", inputSchema: { type: "object", properties: { providerId: { type: "string", description: "Provider ID" }, name: { type: "string", description: "Provider name (optional)" }, enabled: { type: "boolean", description: "Enable provider (optional)" } }, required: ["providerId"] } }, { name: "delete_messaging_provider", description: "Delete messaging provider", inputSchema: { type: "object", properties: { providerId: { type: "string", description: "Provider ID" } }, required: ["providerId"] } }, { name: "create_messaging_message", description: "Create and send a message", inputSchema: { type: "object", properties: { messageId: { type: "string", description: "Message ID (optional)" }, subject: { type: "string", description: "Message subject (optional)" }, content: { type: "string", description: "Message content" }, topics: { type: "array", description: "Topic IDs (optional)", items: { type: "string" } }, users: { type: "array", description: "User IDs (optional)", items: { type: "string" } }, targets: { type: "array", description: "Target IDs (optional)", items: { type: "string" } } }, required: ["content"] } }, { name: "list_messaging_messages", description: "List messaging messages", inputSchema: { type: "object", properties: { queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } } } } }, { name: "get_messaging_message", description: "Get messaging message by ID", inputSchema: { type: "object", properties: { messageId: { type: "string", description: "Message ID" } }, required: ["messageId"] } }, { name: "update_messaging_message", description: "Update messaging message", inputSchema: { type: "object", properties: { messageId: { type: "string", description: "Message ID" }, topics: { type: "array", description: "Topic IDs (optional)", items: { type: "string" } }, users: { type: "array", description: "User IDs (optional)", items: { type: "string" } }, targets: { type: "array", description: "Target IDs (optional)", items: { type: "string" } }, status: { type: "string", description: "Message status (optional)" } }, required: ["messageId"] } }, { name: "delete_messaging_message", description: "Delete messaging message", inputSchema: { type: "object", properties: { messageId: { type: "string", description: "Message ID" } }, required: ["messageId"] } }, { name: "create_messaging_topic", description: "Create messaging topic", inputSchema: { type: "object", properties: { topicId: { type: "string", description: "Topic ID (optional)" }, name: { type: "string", description: "Topic name" }, description: { type: "string", description: "Topic description (optional)" } }, required: ["name"] } }, { name: "list_messaging_topics", description: "List messaging topics", inputSchema: { type: "object", properties: { queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } } } } }, { name: "get_messaging_topic", description: "Get messaging topic by ID", inputSchema: { type: "object", properties: { topicId: { type: "string", description: "Topic ID" } }, required: ["topicId"] } }, { name: "update_messaging_topic", description: "Update messaging topic", inputSchema: { type: "object", properties: { topicId: { type: "string", description: "Topic ID" }, name: { type: "string", description: "Topic name (optional)" }, description: { type: "string", description: "Topic description (optional)" } }, required: ["topicId"] } }, { name: "delete_messaging_topic", description: "Delete messaging topic", inputSchema: { type: "object", properties: { topicId: { type: "string", description: "Topic ID" } }, required: ["topicId"] } }, { name: "create_messaging_subscriber", description: "Create messaging topic subscriber", inputSchema: { type: "object", properties: { topicId: { type: "string", description: "Topic ID" }, subscriberId: { type: "string", description: "Subscriber ID (optional)" }, targetId: { type: "string", description: "Target ID" } }, required: ["topicId", "targetId"] } }, { name: "list_messaging_subscribers", description: "List messaging topic subscribers", inputSchema: { type: "object", properties: { topicId: { type: "string", description: "Topic ID" }, queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } } }, required: ["topicId"] } }, { name: "get_messaging_subscriber", description: "Get messaging subscriber by ID", inputSchema: { type: "object", properties: { topicId: { type: "string", description: "Topic ID" }, subscriberId: { type: "string", description: "Subscriber ID" } }, required: ["topicId", "subscriberId"] } }, { name: "delete_messaging_subscriber", description: "Delete messaging subscriber", inputSchema: { type: "object", properties: { topicId: { type: "string", description: "Topic ID" }, subscriberId: { type: "string", description: "Subscriber ID" } }, required: ["topicId", "subscriberId"] } }, // Function Execution { name: "list_executions", description: "List function executions with logs", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" }, limit: { type: "number", description: "Number of executions to return (optional, max 100)", default: 25 } }, required: ["functionId"] } }, { name: "get_execution", description: "Get details of a specific function execution", inputSchema: { type: "object", properties: { functionId: { type: "string", description: "Function ID" }, executionId: { type: "string", description: "Execution ID" } }, required: ["functionId", "executionId"] } }, // Health Monitoring { name: "get_health", description: "Get overall health status of Appwrite services", inputSchema: { type: "object", properties: {} } }, // Bulk Operations { name: "bulk_create_users", description: "Create multiple users at once", inputSchema: { type: "object", properties: { users: { type: "array", description: "Array of user objects to create", items: { type: "object", properties: { userId: { type: "string", description: "User ID (optional)" }, email: { type: "string", description: "User email" }, phone: { type: "string", description: "User phone number (optional)" }, password: { type: "string", description: "User password" }, name: { type: "string", description: "User name (optional)" } }, required: ["email", "password"] } } }, required: ["users"] } }, { name: "bulk_update_users", description: "Update multiple users at once", inputSchema: { type: "object", properties: { updates: { type: "array", description: "Array of user updates", items: { type: "object", properties: { userId: { type: "string", description: "User ID" }, email: { type: "string", description: "New email (optional)" }, name: { type: "string", description: "New name (optional)" }, password: { type: "string", description: "New password (optional)" } }, required: ["userId"] } } }, required: ["updates"] } }, { name: "bulk_delete_users", description: "Delete multiple users at once", inputSchema: { type: "object", properties: { userIds: { type: "array", description: "Array of user IDs to delete", items: { type: "string" } } }, required: ["userIds"] } }, { name: "bulk_create_documents", description: "Create multiple documents at once", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, documents: { type: "array", description: "Array of documents to create", items: { type: "object", properties: { documentId: { type: "string", description: "Document ID (optional)" }, data: { type: "object", description: "Document data" }, permissions: { type: "array", description: "Document permissions (optional)", items: { type: "string" } } }, required: ["data"] } } }, required: ["databaseId", "collectionId", "documents"] } }, { name: "bulk_update_documents", description: "Update multiple documents at once", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, updates: { type: "array", description: "Array of document updates", items: { type: "object", properties: { documentId: { type: "string", description: "Document ID" }, data: { type: "object", description: "Updated document data" }, permissions: { type: "array", description: "Document permissions (optional)", items: { type: "string" } } }, required: ["documentId", "data"] } } }, required: ["databaseId", "collectionId", "updates"] } }, { name: "bulk_delete_documents", description: "Delete multiple documents at once", inputSchema: { type: "object", properties: { databaseId: { type: "string", description: "ID of the database" }, collectionId: { type: "string", description: "ID of the collection" }, documentIds: { type: "array", description: "Array of document IDs to delete", items: { type: "string" } } }, required: ["databaseId", "collectionId", "documentIds"] } } ] }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!this.config || !this.databases) { throw new McpError( ErrorCode.InternalError, "Appwrite not configured. Please set environment variables: APPWRITE_PROJECT_ID, APPWRITE_API_ENDPOINT, and APPWRITE_API_KEY. For Claude Code CLI, create a .env file in your working directory. For Cursor IDE, add them to your MCP server configuration in settings.json." ); } try { switch (request.params.name) { // Database Operations case "create_database": return await this.createDatabase(request.params.arguments); case "list_databases": return await this.listDatabases(); case "delete_database": return await this.deleteDatabase(request.params.arguments); // Collection Operations case "create_collection": return await this.createCollection(request.params.arguments); case "list_collections": return await this.listCollections(request.params.arguments); case "update_collection": return await this.updateCollection(request.params.arguments); case "delete_collection": return await this.deleteCollection(request.params.arguments); // Attribute Operations case "create_attribute": return await this.createAttribute(request.params.arguments); case "bulk_create_attributes": return await this.bulkCreateAttributes(request.params.arguments); case "bulk_delete_attributes": return await this.bulkDeleteAttributes(request.params.arguments); case "list_attributes": return await this.listAttributes(request.params.arguments); case "delete_attribute": return await this.deleteAttribute(request.params.arguments); case "get_attribute": return await this.getAttribute(request.params.arguments); case "update_attribute": return await this.updateAttribute(request.params.arguments); case "get_collection": return await this.getCollection(request.params.arguments); // Index Operations case "create_index": return await this.createIndex(request.params.arguments); case "list_indexes": return await this.listIndexes(request.params.arguments); case "delete_index": return await this.deleteIndex(request.params.arguments); case "get_index": return await this.getIndex(request.params.arguments); // Document Operations case "create_document": return await this.createDocument(request.params.arguments); case "get_document": return await this.getDocument(request.params.arguments); case "list_documents": return await this.listDocuments(request.params.arguments); case "update_document": return await this.updateDocument(request.params.arguments); case "delete_document": return await this.deleteDocument(request.params.arguments); // User Management Operations case "create_user": return await this.createUser(request.params.arguments); case "list_users": return await this.listUsers(request.params.arguments); case "get_user": return await this.getUser(request.params.arguments); case "update_user": return await this.updateUser(request.params.arguments); case "delete_user": return await this.deleteUser(request.params.arguments); case "update_user_preferences": return await this.updateUserPreferences(request.params.arguments); // Storage Operations case "create_bucket": return await this.createBucket(request.params.arguments); case "list_buckets": return await this.listBuckets(); case "get_bucket": return await this.getBucket(request.params.arguments); case "update_bucket": return await this.updateBucket(request.params.arguments); case "delete_bucket": return await this.deleteBucket(request.params.arguments); case "list_files": return await this.listFiles(request.params.arguments); case "get_file": return await this.getFile(request.params.arguments); case "get_file_url": return await this.getFileUrl(request.params.arguments); case "delete_file": return await this.deleteFile(request.params.arguments); case "create_file": return await this.createFile(request.params.arguments); case "update_file": return await this.updateFile(request.params.arguments); // Function Operations case "create_function": return await this.createFunction(request.params.arguments); case "list_functions": return await this.listFunctions(); case "get_function": return await this.getFunction(request.params.arguments); case "update_function": return await this.updateFunction(request.params.arguments); case "delete_function": return await this.deleteFunction(request.params.arguments); case "list_function_deployments": return await this.listFunctionDeployments(request.params.arguments); case "delete_function_deployment": return await this.deleteFunctionDeployment(request.params.arguments); case "create_function_variable": return await this.createFunctionVariable(request.params.arguments); case "list_function_variables": return await this.listFunctionVariables(request.params.arguments); case "get_function_variable": return await this.getFunctionVariable(request.params.arguments); case "update_function_variable": return await this.updateFunctionVariable(request.params.arguments); case "delete_function_variable": return await this.deleteFunctionVariable(request.params.arguments); // Team Operations case "create_team": return await this.createTeam(request.params.arguments); case "list_teams": return await this.listTeams(); case "get_team": return await this.getTeam(request.params.arguments); case "update_team": return await this.updateTeam(request.params.arguments); case "delete_team": return await this.deleteTeam(request.params.arguments); // Messaging Operations case "create_messaging_provider": return await this.createMessagingProvider(request.params.arguments); case "list_messaging_providers": return await this.listMessagingProviders(); case "get_messaging_provider": return await this.getMessagingProvider(request.params.arguments); case "update_messaging_provider": return await this.updateMessagingProvider(request.params.arguments); case "delete_messaging_provider": return await this.deleteMessagingProvider(request.params.arguments); case "create_messaging_message": return await this.createMessagingMessage(request.params.arguments); case "list_messaging_messages": return await this.listMessagingMessages(request.params.arguments); case "get_messaging_message": return await this.getMessagingMessage(request.params.arguments); case "update_messaging_message": return await this.updateMessagingMessage(request.params.arguments); case "delete_messaging_message": return await this.deleteMessagingMessage(request.params.arguments); case "create_messaging_topic": return await this.createMessagingTopic(request.params.arguments); case "list_messaging_topics": return await this.listMessagingTopics(request.params.arguments); case "get_messaging_topic": return await this.getMessagingTopic(request.params.arguments); case "update_messaging_topic": return await this.updateMessagingTopic(request.params.arguments); case "delete_messaging_topic": return await this.deleteMessagingTopic(request.params.arguments); case "create_messaging_subscriber": return await this.createMessagingSubscriber(request.params.arguments); case "list_messaging_subscribers": return await this.listMessagingSubscribers(request.params.arguments); case "get_messaging_subscriber": return await this.getMessagingSubscriber(request.params.arguments); case "delete_messaging_subscriber": return await this.deleteMessagingSubscriber(request.params.arguments); // Function Execution case "list_executions": return await this.listExecutions(request.params.arguments); case "get_execution": return await this.getExecution(request.params.arguments); // Health Monitoring case "get_health": return await this.getHealth(); // Bulk Operations case "bulk_create_users": return await this.bulkCreateUsers(request.params.arguments); case "bulk_update_users": return await this.bulkUpdateUsers(request.params.arguments); case "bulk_delete_users": return await this.bulkDeleteUsers(request.params.arguments); case "bulk_create_documents": return await this.bulkCreateDocuments(request.params.arguments); case "bulk_update_documents": return await this.bulkUpdateDocuments(request.params.arguments); case "bulk_delete_documents": return await this.bulkDeleteDocuments(request.params.arguments); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } } catch (error) { throw new McpError( ErrorCode.InternalError, `Appwrite operation failed: ${error instanceof Error ? error.message : String(error)}` ); } }); } private async createDatabase(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const databaseId = args.databaseId || ID.unique(); const name = args.name; const database = await this.databases.create(databaseId, name); return { content: [ { type: "text", text: `Database created successfully:\n- ID: ${database.$id}\n- Name: ${database.name}\n- Created: ${database.$createdAt}` } ] }; } private async listDatabases() { if (!this.databases) throw new Error("Databases not initialized"); const databases = await this.databases.list(); const databaseList = databases.databases.map(db => `- ${db.name} (ID: ${db.$id})` ).join('\n'); return { content: [ { type: "text", text: `Databases (${databases.total}):\n${databaseList}` } ] }; } private async deleteDatabase(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const databaseId = args.databaseId; await this.databases.delete(databaseId); return { content: [ { type: "text", text: `Database ${databaseId} deleted successfully` } ] }; } private async createCollection(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const databaseId = args.databaseId; const collectionId = args.collectionId || ID.unique(); const name = args.name; const collection = await this.databases.createCollection(databaseId, collectionId, name); return { content: [ { type: "text", text: `Collection created successfully:\n- ID: ${collection.$id}\n- Name: ${collection.name}\n- Database: ${collection.databaseId}\n- Created: ${collection.$createdAt}` } ] }; } private async listCollections(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const databaseId = args.databaseId; const collections = await this.databases.listCollections(databaseId); const collectionList = collections.collections.map(col => `- ${col.name} (ID: ${col.$id})` ).join('\n'); return { content: [ { type: "text", text: `Collections in database ${databaseId} (${collections.total}):\n${collectionList}` } ] }; } private async updateCollection(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, name, permissions } = args; const collection = await this.databases.updateCollection( databaseId, collectionId, name, permissions ) as any; return { content: [ { type: "text", text: `Collection updated:\n- ID: ${collection.$id}\n- Name: ${collection.name}\n- Documents: ${collection.total}\n- Permissions: ${collection.$permissions?.join(', ') || 'None'}` } ] }; } private async deleteCollection(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const databaseId = args.databaseId; const collectionId = args.collectionId; await this.databases.deleteCollection(databaseId, collectionId); return { content: [ { type: "text", text: `Collection ${collectionId} deleted successfully from database ${databaseId}` } ] }; } // Attribute Operations private async createAttribute(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, key, type, required, default: defaultValue, size, min, max, elements, relatedCollection, relationType, twoWay, twoWayKey } = args; let attribute; switch (type) { case 'string': attribute = await this.databases.createStringAttribute( databaseId, collectionId, key, size || 255, required, defaultValue ); break; case 'integer': attribute = await this.databases.createIntegerAttribute( databaseId, collectionId, key, required, min, max, defaultValue ); break; case 'float': attribute = await this.databases.createFloatAttribute( databaseId, collectionId, key, required, min, max, defaultValue ); break; case 'boolean': attribute = await this.databases.createBooleanAttribute( databaseId, collectionId, key, required, defaultValue ); break; case 'datetime': attribute = await this.databases.createDatetimeAttribute( databaseId, collectionId, key, required, defaultValue ); break; case 'email': attribute = await this.databases.createEmailAttribute( databaseId, collectionId, key, required, defaultValue ); break; case 'ip': attribute = await this.databases.createIpAttribute( databaseId, collectionId, key, required, defaultValue ); break; case 'url': attribute = await this.databases.createUrlAttribute( databaseId, collectionId, key, required, defaultValue ); break; case 'enum': if (!elements || elements.length === 0) { throw new Error("Enum attributes require 'elements' array with allowed values"); } attribute = await this.databases.createEnumAttribute( databaseId, collectionId, key, elements, required, defaultValue ); break; case 'relationship': if (!relatedCollection || !relationType) { throw new Error("Relationship attributes require 'relatedCollection' and 'relationType'"); } attribute = await this.databases.createRelationshipAttribute( databaseId, collectionId, relatedCollection, relationType, twoWay, key, twoWayKey ); break; default: throw new Error(`Unsupported attribute type: ${type}`); } return { content: [ { type: "text", text: `${type.charAt(0).toUpperCase() + type.slice(1)} attribute '${key}' created successfully in collection ${collectionId}` } ] }; } private async bulkCreateAttributes(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, attributes } = args; const results = []; const errors = []; for (const attr of attributes) { try { await this.createAttribute({ databaseId, collectionId, ...attr }); results.push(`✅ ${attr.key} (${attr.type})`); } catch (error) { errors.push(`❌ ${attr.key} (${attr.type}): ${error}`); } } return { content: [ { type: "text", text: `Bulk Attribute Creation Results: Successful (${results.length}): ${results.join('\n')} ${errors.length > 0 ? `Failed (${errors.length}): ${errors.join('\n')}` : ''}` } ] }; } private async bulkDeleteAttributes(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, attributeKeys } = args; const results = []; const errors = []; for (const key of attributeKeys) { try { await this.databases.deleteAttribute(databaseId, collectionId, key); results.push(`✅ ${key}`); } catch (error) { errors.push(`❌ ${key}: ${error}`); } } return { content: [ { type: "text", text: `Bulk Attribute Deletion Results: Successful (${results.length}): ${results.join('\n')} ${errors.length > 0 ? `Failed (${errors.length}): ${errors.join('\n')}` : ''}` } ] }; } private async listAttributes(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId } = args; const attributes = await this.databases.listAttributes(databaseId, collectionId); const attributeList = attributes.attributes.map((attr: any) => `- ${attr.key} (${attr.type}) - Required: ${attr.required}` ).join('\n'); return { content: [ { type: "text", text: `Attributes in collection ${collectionId} (${attributes.total}):\n${attributeList}` } ] }; } private async deleteAttribute(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, key } = args; await this.databases.deleteAttribute(databaseId, collectionId, key); return { content: [ { type: "text", text: `Attribute '${key}' deleted successfully from collection ${collectionId}` } ] }; } // Index Operations private async createIndex(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, key, type, attributes } = args; const index = await this.databases.createIndex(databaseId, collectionId, key, type, attributes); return { content: [ { type: "text", text: `Index '${key}' created successfully in collection ${collectionId}` } ] }; } private async listIndexes(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId } = args; const indexes = await this.databases.listIndexes(databaseId, collectionId); const indexList = indexes.indexes.map((idx: any) => `- ${idx.key} (${idx.type}) - Attributes: ${idx.attributes.join(', ')}` ).join('\n'); return { content: [ { type: "text", text: `Indexes in collection ${collectionId} (${indexes.total}):\n${indexList}` } ] }; } private async deleteIndex(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, key } = args; await this.databases.deleteIndex(databaseId, collectionId, key); return { content: [ { type: "text", text: `Index '${key}' deleted successfully from collection ${collectionId}` } ] }; } private async getAttribute(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, key } = args; const attribute = await this.databases.getAttribute(databaseId, collectionId, key) as any; return { content: [ { type: "text", text: `Attribute Details: - Key: ${attribute.key} - Type: ${attribute.type} - Required: ${attribute.required} - Status: ${attribute.status} ${attribute.size ? `- Size: ${attribute.size}` : ''} ${attribute.min !== undefined ? `- Min: ${attribute.min}` : ''} ${attribute.max !== undefined ? `- Max: ${attribute.max}` : ''} ${attribute.elements ? `- Elements: ${attribute.elements.join(', ')}` : ''} ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}` } ] }; } private async updateAttribute(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, key, type, required, default: defaultValue, size, min, max, elements } = args; let attribute; switch (type) { case 'string': attribute = await this.databases.updateStringAttribute( databaseId, collectionId, key, required, defaultValue, size ); break; case 'integer': attribute = await this.databases.updateIntegerAttribute( databaseId, collectionId, key, required, min, max, defaultValue || null ); break; case 'float': attribute = await this.databases.updateFloatAttribute( databaseId, collectionId, key, required, min, max, defaultValue || null ); break; case 'boolean': attribute = await this.databases.updateBooleanAttribute( databaseId, collectionId, key, required, defaultValue ); break; case 'datetime': attribute = await this.databases.updateDatetimeAttribute( databaseId, collectionId, key, required, defaultValue ); break; case 'email': attribute = await this.databases.updateEmailAttribute( databaseId, collectionId, key, required, defaultValue ); break; case 'ip': attribute = await this.databases.updateIpAttribute( databaseId, collectionId, key, required, defaultValue ); break; case 'url': attribute = await this.databases.updateUrlAttribute( databaseId, collectionId, key, required, defaultValue ); break; case 'enum': if (!elements || elements.length === 0) { throw new Error("Enum attributes require 'elements' array with allowed values"); } attribute = await this.databases.updateEnumAttribute( databaseId, collectionId, key, elements, required, defaultValue ); break; default: throw new Error(`Unsupported attribute type for update: ${type}. Note: Relationship attributes cannot be updated.`); } return { content: [ { type: "text", text: `${type.charAt(0).toUpperCase() + type.slice(1)} attribute '${key}' updated successfully in collection ${collectionId}` } ] }; } private async getCollection(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId } = args; const collection = await this.databases.getCollection(databaseId, collectionId) as any; return { content: [ { type: "text", text: `Collection Details: - ID: ${collection.$id} - Name: ${collection.name} - Database: ${collection.databaseId} - Created: ${collection.$createdAt} - Updated: ${collection.$updatedAt} - Document Security: ${collection.documentSecurity} - Attributes: ${collection.attributes?.length || 0} - Indexes: ${collection.indexes?.length || 0}` } ] }; } // Index Operations private async getIndex(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, key } = args; const index = await this.databases.getIndex(databaseId, collectionId, key) as any; return { content: [ { type: "text", text: `Index Details: - Key: ${index.key} - Type: ${index.type} - Status: ${index.status} - Attributes: ${index.attributes.join(', ')} - Created: ${index.$createdAt} - Updated: ${index.$updatedAt}` } ] }; } // Document Operations private async createDocument(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, documentId, data, permissions } = args; const docId = documentId || ID.unique(); // Validate document against collection schema try { const validation = await this.validateDocumentData(databaseId, collectionId, data); if (!validation.isValid) { throw new Error(`Document validation failed: ${validation.errors.join(', ')}`); } } catch (validationError) { // If validation fails, provide helpful error message throw new Error(`Schema validation error: ${validationError}. Use list_attributes to check collection schema.`); } const document = await this.databases.createDocument( databaseId, collectionId, docId, data, permissions ); return { content: [ { type: "text", text: `Document created successfully:\n- ID: ${document.$id}\n- Collection: ${document.$collectionId}\n- Created: ${document.$createdAt}\n- Data: ${JSON.stringify(data, null, 2)}` } ] }; } private async getDocument(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, documentId } = args; const document = await this.databases.getDocument(databaseId, collectionId, documentId); return { content: [ { type: "text", text: `Document ${documentId}:\n${JSON.stringify(document, null, 2)}` } ] }; } private async listDocuments(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, queries, limit, offset } = args; // Build query array with limit and offset const allQueries = [...(queries || [])]; if (limit !== undefined) { allQueries.push(Query.limit(limit)); } if (offset !== undefined) { allQueries.push(Query.offset(offset)); } const documents = await this.databases.listDocuments(databaseId, collectionId, allQueries) as any; const currentLimit = limit || 25; const currentOffset = offset || 0; const showing = Math.min(documents.documents.length, currentLimit); const totalDocs = documents.total; return { content: [ { type: "text", text: `Documents in collection ${collectionId}:\n- Total: ${totalDocs}\n- Showing: ${showing} (offset: ${currentOffset}, limit: ${currentLimit})\n- Has more: ${currentOffset + showing < totalDocs}\n\n${JSON.stringify(documents.documents, null, 2)}` } ] }; } private async updateDocument(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, documentId, data, permissions } = args; const document = await this.databases.updateDocument( databaseId, collectionId, documentId, data, permissions ); return { content: [ { type: "text", text: `Document ${documentId} updated successfully:\n${JSON.stringify(document, null, 2)}` } ] }; } private async deleteDocument(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, documentId } = args; await this.databases.deleteDocument(databaseId, collectionId, documentId); return { content: [ { type: "text", text: `Document ${documentId} deleted successfully from collection ${collectionId}` } ] }; } // User Management Operations private async createUser(args: any) { if (!this.users) throw new Error("Users service not initialized"); const { userId, email, phone, password, name } = args; const uid = userId || ID.unique(); const user = await this.users.create(uid, email, phone, password, name); return { content: [ { type: "text", text: `User created successfully:\n- ID: ${user.$id}\n- Email: ${user.email}\n- Name: ${user.name}\n- Created: ${user.$createdAt}` } ] }; } private async listUsers(args: any) { if (!this.users) throw new Error("Users service not initialized"); const { queries, limit, offset } = args; // Build query array with limit and offset const allQueries = [...(queries || [])]; if (limit !== undefined) { allQueries.push(Query.limit(limit)); } if (offset !== undefined) { allQueries.push(Query.offset(offset)); } const users = await this.users.list(allQueries) as any; const header = `${'Name'.padEnd(20)} ${'Email'.padEnd(30)} ${'Phone'.padEnd(15)} ${'Email Ver'.padEnd(10)} ${'Phone Ver'.padEnd(10)} ID`; const separator = '-'.repeat(100); const userList = users.users.map((user: any) => { const name = (user.name || 'No name').padEnd(20).substring(0, 20); const email = (user.email || 'No email').padEnd(30).substring(0, 30); const phone = (user.phone || 'No phone').padEnd(15).substring(0, 15); const emailVer = (user.emailVerification ? '✅ Yes' : '❌ No').padEnd(10); const phoneVer = (user.phoneVerification ? '✅ Yes' : '❌ No').padEnd(10); return `${name} ${email} ${phone} ${emailVer} ${phoneVer} ${user.$id}`; }).join('\n'); const currentLimit = limit || 25; const currentOffset = offset || 0; const showing = users.users.length; const totalUsers = users.total; return { content: [ { type: "text", text: `Users:\n- Total: ${totalUsers}\n- Showing: ${showing} (offset: ${currentOffset}, limit: ${currentLimit})\n- Has more: ${currentOffset + showing < totalUsers}\n\n${header}\n${separator}\n${userList}` } ] }; } private async getUser(args: any) { if (!this.users) throw new Error("Users service not initialized"); const { userId } = args; const user = await this.users.get(userId); return { content: [ { type: "text", text: `User ${userId}:\n${JSON.stringify(user, null, 2)}` } ] }; } private async updateUser(args: any) { if (!this.users) throw new Error("Users service not initialized"); const { userId, email, name, password } = args; if (!email && !name && !password) { throw new Error("At least one field (email, name, or password) must be provided"); } const updates: string[] = []; if (email) { await this.users.updateEmail(userId, email); updates.push(`email updated to: ${email}`); } if (name) { await this.users.updateName(userId, name); updates.push(`name updated to: ${name}`); } if (password) { await this.users.updatePassword(userId, password); updates.push(`password updated successfully`); } return { content: [ { type: "text", text: `User ${userId} - ${updates.join(', ')}` } ] }; } private async deleteUser(args: any) { if (!this.users) throw new Error("Users service not initialized"); const { userId } = args; await this.users.delete(userId); return { content: [ { type: "text", text: `User ${userId} deleted successfully` } ] }; } private async updateUserPreferences(args: any) { if (!this.users) throw new Error("Users service not initialized"); const { userId, prefs } = args; const result = await this.users.updatePrefs(userId, prefs) as any; return { content: [ { type: "text", text: `User ${userId} preferences updated successfully` } ] }; } // Storage Operations private async createBucket(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); const { bucketId, name, permissions, fileSecurity, enabled } = args; const bid = bucketId || ID.unique(); const bucket = await this.storage.createBucket(bid, name, permissions, fileSecurity, enabled); return { content: [ { type: "text", text: `Bucket created successfully:\n- ID: ${bucket.$id}\n- Name: ${bucket.name}\n- Created: ${bucket.$createdAt}` } ] }; } private async listBuckets() { if (!this.storage) throw new Error("Storage service not initialized"); const buckets = await this.storage.listBuckets(); const bucketList = buckets.buckets.map((bucket: any) => `- ${bucket.name} (ID: ${bucket.$id})` ).join('\n'); return { content: [ { type: "text", text: `Storage buckets (${buckets.total}):\n${bucketList}` } ] }; } private async getBucket(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); const { bucketId } = args; const bucket = await this.storage.getBucket(bucketId); return { content: [ { type: "text", text: `Bucket ${bucketId}:\n${JSON.stringify(bucket, null, 2)}` } ] }; } private async updateBucket(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); const { bucketId, name, permissions, fileSecurity, enabled } = args; const bucket = await this.storage.updateBucket(bucketId, name, permissions, fileSecurity, enabled); return { content: [ { type: "text", text: `Bucket ${bucketId} updated successfully` } ] }; } private async deleteBucket(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); const { bucketId } = args; await this.storage.deleteBucket(bucketId); return { content: [ { type: "text", text: `Bucket ${bucketId} deleted successfully` } ] }; } private async listFiles(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); const { bucketId } = args; const files = await this.storage.listFiles(bucketId); const fileList = files.files.map((file: any) => `- ${file.name} (${file.sizeOriginal} bytes) - ID: ${file.$id}` ).join('\n'); return { content: [ { type: "text", text: `Files in bucket ${bucketId} (${files.total}):\n${fileList}` } ] }; } private async getFile(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); const { bucketId, fileId } = args; const file = await this.storage.getFile(bucketId, fileId) as any; return { content: [ { type: "text", text: `File ${fileId}:\n${JSON.stringify(file, null, 2)}` } ] }; } private async getFileUrl(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); const { bucketId, fileId, type, width, height, gravity, quality, borderWidth, borderColor, borderRadius, opacity, rotation, background, output } = args; let url: string; let urlType: string; switch (type) { case 'download': url = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/download?project=${this.config?.projectId}`; urlType = 'Download'; break; case 'view': url = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/view?project=${this.config?.projectId}`; urlType = 'View'; break; case 'preview': // Build query parameters for preview const params = new URLSearchParams(); if (width !== undefined) params.append('width', width.toString()); if (height !== undefined) params.append('height', height.toString()); if (gravity) params.append('gravity', gravity); if (quality !== undefined) params.append('quality', quality.toString()); if (borderWidth !== undefined) params.append('borderWidth', borderWidth.toString()); if (borderColor) params.append('borderColor', borderColor); if (borderRadius !== undefined) params.append('borderRadius', borderRadius.toString()); if (opacity !== undefined) params.append('opacity', opacity.toString()); if (rotation !== undefined) params.append('rotation', rotation.toString()); if (background) params.append('background', background); if (output) params.append('output', output); params.append('project', this.config?.projectId || ''); url = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/preview?${params.toString()}`; urlType = 'Preview'; break; default: throw new Error(`Invalid URL type: ${type}. Must be 'download', 'preview', or 'view'`); } return { content: [ { type: "text", text: `${urlType} URL for file ${fileId}:\n${url}` } ] }; } private async deleteFile(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); const { bucketId, fileId } = args; await this.storage.deleteFile(bucketId, fileId); return { content: [ { type: "text", text: `File ${fileId} deleted successfully from bucket ${bucketId}` } ] }; } private async createFile(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); const { bucketId, fileId, filePath, permissions } = args; const fid = fileId || ID.unique(); try { // Check if file exists if (!existsSync(filePath)) { throw new Error(`File not found: ${filePath}`); } // Create InputFile from path and upload const fileName = filePath.split(/[\\/]/).pop() || 'file'; const file = InputFile.fromPath(filePath, fileName); const result = await this.storage.createFile(bucketId, fid, file, permissions) as any; return { content: [ { type: "text", text: `File uploaded successfully:\n- File ID: ${result.$id}\n- Name: ${result.name}\n- Size: ${result.sizeOriginal} bytes\n- MIME Type: ${result.mimeType}` } ] }; } catch (error: any) { return { content: [ { type: "text", text: `Error uploading file: ${error.message}` } ] }; } } private async updateFile(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); const { bucketId, fileId, name, permissions } = args; const file = await this.storage.updateFile(bucketId, fileId, name, permissions) as any; return { content: [ { type: "text", text: `File ${fileId} metadata updated successfully` } ] }; } // Function Operations private async createFunction(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, name, runtime, execute, events, schedule, timeout } = args; const fid = functionId || ID.unique(); const func = await this.functions.create(fid, name, runtime, execute, events, schedule, timeout); return { content: [ { type: "text", text: `Function created successfully:\n- ID: ${func.$id}\n- Name: ${func.name}\n- Runtime: ${func.runtime}\n- Created: ${func.$createdAt}` } ] }; } private async listFunctions() { if (!this.functions) throw new Error("Functions service not initialized"); const functions = await this.functions.list(); const functionList = functions.functions.map((func: any) => `- ${func.name} (${func.runtime}) - ID: ${func.$id}` ).join('\n'); return { content: [ { type: "text", text: `Functions (${functions.total}):\n${functionList}` } ] }; } private async getFunction(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId } = args; const func = await this.functions.get(functionId); return { content: [ { type: "text", text: `Function ${functionId}:\n${JSON.stringify(func, null, 2)}` } ] }; } private async updateFunction(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, name, runtime, execute, events, schedule, timeout } = args; const func = await this.functions.update(functionId, name, runtime, execute, events, schedule, timeout); return { content: [ { type: "text", text: `Function ${functionId} updated successfully` } ] }; } private async deleteFunction(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId } = args; await this.functions.delete(functionId); return { content: [ { type: "text", text: `Function ${functionId} deleted successfully` } ] }; } private async listFunctionDeployments(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, queries, search } = args; const deployments = await this.functions.listDeployments(functionId, queries, search) as any; const deploymentList = deployments.deployments.map((deployment: any) => `- ${deployment.status} - ID: ${deployment.$id} - Created: ${deployment.$createdAt}` ).join('\n'); return { content: [ { type: "text", text: `Function ${functionId} deployments (${deployments.total}):\n${deploymentList}` } ] }; } private async deleteFunctionDeployment(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, deploymentId } = args; await this.functions.deleteDeployment(functionId, deploymentId); return { content: [ { type: "text", text: `Function ${functionId} deployment ${deploymentId} deleted successfully` } ] }; } private async createFunctionVariable(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, key, value, secret } = args; const variable = await this.functions.createVariable(functionId, key, value) as any; return { content: [ { type: "text", text: `Variable created for function ${functionId}:\n- Key: ${variable.key}\n- ID: ${variable.$id}\n- Secret: ${variable.secret ? 'Yes' : 'No'}` } ] }; } private async listFunctionVariables(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId } = args; const variables = await this.functions.listVariables(functionId) as any; const variableList = variables.variables.map((variable: any) => `- ${variable.key} (${variable.secret ? 'Secret' : 'Public'}) - ID: ${variable.$id}` ).join('\n'); return { content: [ { type: "text", text: `Function ${functionId} variables (${variables.total}):\n${variableList}` } ] }; } private async getFunctionVariable(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, variableId } = args; const variable = await this.functions.getVariable(functionId, variableId) as any; return { content: [ { type: "text", text: `Function ${functionId} variable ${variableId}:\n${JSON.stringify(variable, null, 2)}` } ] }; } private async updateFunctionVariable(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, variableId, key, value, secret } = args; const variable = await this.functions.updateVariable(functionId, variableId, key, value) as any; return { content: [ { type: "text", text: `Function ${functionId} variable ${variableId} updated successfully` } ] }; } private async deleteFunctionVariable(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, variableId } = args; await this.functions.deleteVariable(functionId, variableId); return { content: [ { type: "text", text: `Function ${functionId} variable ${variableId} deleted successfully` } ] }; } // Team Operations private async createTeam(args: any) { if (!this.teams) throw new Error("Teams service not initialized"); const { teamId, name, roles } = args; const tid = teamId || ID.unique(); const team = await this.teams.create(tid, name, roles); return { content: [ { type: "text", text: `Team created successfully:\n- ID: ${team.$id}\n- Name: ${team.name}\n- Created: ${team.$createdAt}` } ] }; } private async listTeams() { if (!this.teams) throw new Error("Teams service not initialized"); const teams = await this.teams.list(); const teamList = teams.teams.map((team: any) => `- ${team.name} - ID: ${team.$id}` ).join('\n'); return { content: [ { type: "text", text: `Teams (${teams.total}):\n${teamList}` } ] }; } private async getTeam(args: any) { if (!this.teams) throw new Error("Teams service not initialized"); const { teamId } = args; const team = await this.teams.get(teamId); return { content: [ { type: "text", text: `Team ${teamId}:\n${JSON.stringify(team, null, 2)}` } ] }; } private async updateTeam(args: any) { if (!this.teams) throw new Error("Teams service not initialized"); const { teamId, name } = args; const team = await this.teams.updateName(teamId, name); return { content: [ { type: "text", text: `Team ${teamId} name updated to: ${name}` } ] }; } private async deleteTeam(args: any) { if (!this.teams) throw new Error("Teams service not initialized"); const { teamId } = args; await this.teams.delete(teamId); return { content: [ { type: "text", text: `Team ${teamId} deleted successfully` } ] }; } // Messaging Operations private async createMessagingProvider(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { providerId, name, type, enabled } = args; const pid = providerId || ID.unique(); try { let result; // Create provider based on type switch (type) { case 'email': // Create SMTP provider (most common email provider) result = await this.messaging.createSmtpProvider( pid, name, 'smtp.gmail.com', // default host 587, // default port '', // username (to be configured later) '', // password (to be configured later) 'tls' as any, // encryption false, // autoTLS '' as any, // mailer enabled || true ) as any; break; case 'sms': // Create Twilio provider (most common SMS provider) result = await this.messaging.createTwilioProvider( pid, name, '', // Account SID (to be configured later) '', // Auth token (to be configured later) '', // From number (to be configured later) enabled || true ) as any; break; case 'push': // Create FCM provider (most common push provider) result = await this.messaging.createFcmProvider( pid, name, {}, // Service account JSON (to be configured later) enabled || true ) as any; break; default: throw new Error(`Unsupported provider type: ${type}. Use 'email', 'sms', or 'push'`); } return { content: [ { type: "text", text: `${type.toUpperCase()} provider created:\n- ID: ${result.$id}\n- Name: ${result.name}\n- Type: ${result.type}\n- Enabled: ${result.enabled}\n- Note: Configure credentials in Appwrite Console` } ] }; } catch (error: any) { return { content: [ { type: "text", text: `Error creating ${type} provider: ${error.message}` } ] }; } } private async listMessagingProviders() { if (!this.messaging) throw new Error("Messaging service not initialized"); try { const providers = await this.messaging.listProviders() as any; if (providers.providers.length === 0) { return { content: [ { type: "text", text: "No messaging providers configured" } ] }; } const providerList = providers.providers.map((provider: any) => `- ${provider.name} (${provider.type}) - ID: ${provider.$id} - Enabled: ${provider.enabled}` ).join('\n'); return { content: [ { type: "text", text: `Messaging providers (${providers.providers.length}):\n${providerList}` } ] }; } catch (error: any) { return { content: [ { type: "text", text: `Error listing providers: ${error.message}` } ] }; } } private async getMessagingProvider(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { providerId } = args; const provider = await this.messaging.getProvider(providerId) as any; return { content: [ { type: "text", text: `Messaging provider ${providerId}:\n${JSON.stringify(provider, null, 2)}` } ] }; } private async updateMessagingProvider(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { providerId, name, enabled } = args; try { // First get the provider to determine its type const provider = await this.messaging.getProvider(providerId) as any; let result; switch (provider.type) { case 'smtp': result = await this.messaging.updateSmtpProvider( providerId, name || provider.name, provider.host, provider.port, provider.username, provider.password, provider.encryption, provider.autoTLS, provider.mailer, enabled !== undefined ? enabled : provider.enabled ) as any; break; case 'twilio': result = await this.messaging.updateTwilioProvider( providerId, name || provider.name, provider.accountSid, provider.authToken, provider.from, enabled !== undefined ? enabled : provider.enabled ) as any; break; case 'fcm': result = await this.messaging.updateFcmProvider( providerId, name || provider.name, provider.serviceAccountJSON, enabled !== undefined ? enabled : provider.enabled ) as any; break; default: throw new Error(`Unsupported provider type: ${provider.type}`); } return { content: [ { type: "text", text: `${provider.type.toUpperCase()} provider updated:\n- ID: ${result.$id}\n- Name: ${result.name}\n- Enabled: ${result.enabled}` } ] }; } catch (error: any) { return { content: [ { type: "text", text: `Error updating provider: ${error.message}` } ] }; } } private async deleteMessagingProvider(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { providerId } = args; await this.messaging.deleteProvider(providerId); return { content: [ { type: "text", text: `Messaging provider ${providerId} deleted successfully` } ] }; } private async createMessagingMessage(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { messageId, subject, content, topics, users, targets } = args; const mid = messageId || ID.unique(); try { // Check if any email providers are available const providers = await this.messaging.listProviders() as any; const emailProviders = providers.providers.filter((p: any) => p.type === 'smtp' || p.type === 'mailgun' || p.type === 'sendgrid' ); if (emailProviders.length === 0) { throw new Error("No email providers configured. Please create an email provider first using create_messaging_provider."); } // Create email message (assuming email provider since most common) const result = await this.messaging.createEmail( mid, subject || "No Subject", content, topics, users, targets ) as any; return { content: [ { type: "text", text: `Email message created and queued:\n- ID: ${result.$id}\n- Subject: ${result.subject}\n- Status: ${result.status}\n- Scheduled: ${result.scheduledAt || 'Immediate'}` } ] }; } catch (error: any) { return { content: [ { type: "text", text: `Error creating message: ${error.message}` } ] }; } } private async listMessagingMessages(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { queries } = args; const messages = await this.messaging.listMessages(queries) as any; const messageList = messages.messages.map((message: any) => `- ${message.subject || 'No subject'} - Status: ${message.status} - ID: ${message.$id}` ).join('\n'); return { content: [ { type: "text", text: `Messages (${messages.total}):\n${messageList}` } ] }; } private async getMessagingMessage(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { messageId } = args; const message = await this.messaging.getMessage(messageId) as any; return { content: [ { type: "text", text: `Message ${messageId}:\n${JSON.stringify(message, null, 2)}` } ] }; } private async updateMessagingMessage(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { messageId, topics, users, targets, status } = args; // Note: Update message functionality depends on message type return { content: [ { type: "text", text: `Message ${messageId} update requested - use specific message update methods` } ] }; } private async deleteMessagingMessage(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { messageId } = args; // Note: Use specific delete methods for different message types // await this.messaging.deleteMessage(messageId); return { content: [ { type: "text", text: `Message ${messageId} deleted successfully` } ] }; } private async createMessagingTopic(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { topicId, name, description } = args; const tid = topicId || ID.unique(); const topic = await this.messaging.createTopic(tid, name, description) as any; return { content: [ { type: "text", text: `Topic created:\n- ID: ${topic.$id}\n- Name: ${topic.name}\n- Description: ${topic.description || 'No description'}` } ] }; } private async listMessagingTopics(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { queries } = args; const topics = await this.messaging.listTopics(queries) as any; const topicList = topics.topics.map((topic: any) => `- ${topic.name} - ID: ${topic.$id}` ).join('\n'); return { content: [ { type: "text", text: `Topics (${topics.total}):\n${topicList}` } ] }; } private async getMessagingTopic(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { topicId } = args; const topic = await this.messaging.getTopic(topicId) as any; return { content: [ { type: "text", text: `Topic ${topicId}:\n${JSON.stringify(topic, null, 2)}` } ] }; } private async updateMessagingTopic(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { topicId, name, description } = args; const topic = await this.messaging.updateTopic(topicId, name, description) as any; return { content: [ { type: "text", text: `Topic ${topicId} updated successfully` } ] }; } private async deleteMessagingTopic(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { topicId } = args; await this.messaging.deleteTopic(topicId); return { content: [ { type: "text", text: `Topic ${topicId} deleted successfully` } ] }; } private async createMessagingSubscriber(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { topicId, subscriberId, targetId } = args; const sid = subscriberId || ID.unique(); const subscriber = await this.messaging.createSubscriber(topicId, sid, targetId) as any; return { content: [ { type: "text", text: `Subscriber created:\n- ID: ${subscriber.$id}\n- Topic: ${topicId}\n- Target: ${targetId}` } ] }; } private async listMessagingSubscribers(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { topicId, queries } = args; const subscribers = await this.messaging.listSubscribers(topicId, queries) as any; const subscriberList = subscribers.subscribers.map((subscriber: any) => `- Target: ${subscriber.targetId} - ID: ${subscriber.$id}` ).join('\n'); return { content: [ { type: "text", text: `Subscribers for topic ${topicId} (${subscribers.total}):\n${subscriberList}` } ] }; } private async getMessagingSubscriber(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { topicId, subscriberId } = args; const subscriber = await this.messaging.getSubscriber(topicId, subscriberId) as any; return { content: [ { type: "text", text: `Subscriber ${subscriberId}:\n${JSON.stringify(subscriber, null, 2)}` } ] }; } private async deleteMessagingSubscriber(args: any) { if (!this.messaging) throw new Error("Messaging service not initialized"); const { topicId, subscriberId } = args; await this.messaging.deleteSubscriber(topicId, subscriberId); return { content: [ { type: "text", text: `Subscriber ${subscriberId} deleted from topic ${topicId}` } ] }; } // Smart Schema Operations private async autoDetectSchema(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionName, sampleData, collectionId } = args; const cid = collectionId || ID.unique(); // Analyze sample data to determine schema const schema = this.analyzeDataStructure(sampleData); // Create collection const collection = await this.databases.createCollection(databaseId, cid, collectionName); // Create attributes based on detected schema const createdAttributes = []; for (const attr of schema.attributes) { try { switch (attr.type) { case 'string': await this.databases.createStringAttribute( databaseId, cid, attr.key, attr.size || 255, attr.required || false ); break; case 'integer': await this.databases.createIntegerAttribute( databaseId, cid, attr.key, attr.required || false ); break; case 'boolean': await this.databases.createBooleanAttribute( databaseId, cid, attr.key, attr.required || false ); break; case 'email': await this.databases.createEmailAttribute( databaseId, cid, attr.key, attr.required || false ); break; case 'datetime': await this.databases.createDatetimeAttribute( databaseId, cid, attr.key, attr.required || false ); break; } createdAttributes.push(attr); } catch (error) { console.error(`Failed to create attribute ${attr.key}:`, error); } } // Suggest indexes for commonly queried fields const suggestedIndexes = schema.suggestedIndexes; return { content: [ { type: "text", text: `Schema auto-detected and collection created successfully: - Collection: ${collectionName} (ID: ${cid}) - Attributes created: ${createdAttributes.length} - Detected types: ${createdAttributes.map(a => `${a.key} (${a.type})`).join(', ')} - Suggested indexes: ${suggestedIndexes.join(', ')} Schema Analysis: ${JSON.stringify(schema, null, 2)}` } ] }; } private analyzeDataStructure(sampleData: any[]): any { const fieldTypes: { [key: string]: { type: string; required: boolean; size?: number; samples: any[] } } = {}; const totalRecords = sampleData.length; // Analyze each sample document sampleData.forEach(doc => { Object.keys(doc).forEach(key => { if (!fieldTypes[key]) { fieldTypes[key] = { type: 'unknown', required: false, samples: [] }; } fieldTypes[key].samples.push(doc[key]); }); }); // Determine types and properties const attributes = Object.keys(fieldTypes).map(key => { const field = fieldTypes[key]; const samples = field.samples; const nonNullSamples = samples.filter(s => s !== null && s !== undefined); // Filter out empty/meaningless values for better required field detection const meaningfulSamples = samples.filter(s => s !== null && s !== undefined && s !== "" && (typeof s === 'string' ? s.trim() !== "" : true) ); // Calculate required percentage based on meaningful data const requiredPercentage = meaningfulSamples.length / totalRecords; const required = requiredPercentage > 0.7; // 70% threshold for required (more lenient) // Detect type let type = 'string'; let size = 255; if (meaningfulSamples.length > 0) { const firstSample = meaningfulSamples[0]; if (typeof firstSample === 'boolean') { type = 'boolean'; } else if (typeof firstSample === 'number' && Number.isInteger(firstSample)) { type = 'integer'; } else if (typeof firstSample === 'string') { // Check for email pattern if (meaningfulSamples.some(s => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s))) { type = 'email'; } // Check for datetime pattern else if (meaningfulSamples.some(s => !isNaN(Date.parse(s)))) { type = 'datetime'; } else { type = 'string'; // Calculate max string length from meaningful samples const maxLength = Math.max(...meaningfulSamples.map(s => String(s).length)); size = Math.max(255, Math.ceil(maxLength * 1.2)); // 20% buffer } } } return { key, type, required, size: type === 'string' ? size : undefined }; }); // Suggest indexes for common query fields const suggestedIndexes = attributes .filter(attr => ['email', 'string'].includes(attr.type) && attr.key.match(/(id|email|name|slug|status|type)/i)) .map(attr => attr.key); return { attributes, suggestedIndexes, totalRecords, analysis: fieldTypes }; } private async suggestIndexes(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, queryPatterns } = args; // Get collection attributes const attributes = await this.databases.listAttributes(databaseId, collectionId); const attributeMap = new Map(attributes.attributes.map((attr: any) => [attr.key, attr])); // Get existing indexes const existingIndexes = await this.databases.listIndexes(databaseId, collectionId); const existingIndexKeys = new Set(existingIndexes.indexes.map((idx: any) => idx.key)); const suggestions: any[] = []; // Standard suggestions based on attribute types attributes.attributes.forEach((attr: any) => { const indexKey = `idx_${attr.key}`; if (existingIndexKeys.has(indexKey)) return; if (attr.type === 'email' || attr.key.includes('email')) { suggestions.push({ key: indexKey, type: 'unique', attributes: [attr.key], reason: 'Email fields should have unique indexes for fast lookups and uniqueness constraints' }); } else if (attr.key.match(/(id|name|slug|status|type|category)/i)) { suggestions.push({ key: indexKey, type: 'key', attributes: [attr.key], reason: `Common query field '${attr.key}' would benefit from indexing` }); } else if (attr.type === 'string' && attr.key.match(/(title|description|content|body)/i)) { suggestions.push({ key: `fulltext_${attr.key}`, type: 'fulltext', attributes: [attr.key], reason: `Text field '${attr.key}' would benefit from full-text search indexing` }); } }); // Analyze query patterns if provided if (queryPatterns && queryPatterns.length > 0) { queryPatterns.forEach((pattern: string, index: number) => { const fields = this.extractFieldsFromQuery(pattern); if (fields.length > 1) { const compositeKey = `composite_${fields.join('_')}`; if (!existingIndexKeys.has(compositeKey)) { suggestions.push({ key: compositeKey, type: 'key', attributes: fields, reason: `Composite index for query pattern: "${pattern}"` }); } } }); } // Remove duplicates and limit suggestions const uniqueSuggestions = suggestions.slice(0, 10); return { content: [ { type: "text", text: `Index Recommendations for Collection ${collectionId}: Found ${attributes.total} attributes, ${existingIndexes.total} existing indexes Recommended Indexes (${uniqueSuggestions.length}): ${uniqueSuggestions.map(s => `• ${s.key} (${s.type}) on [${s.attributes.join(', ')}] Reason: ${s.reason}` ).join('\n\n')} Existing Indexes: ${existingIndexes.indexes.map((idx: any) => `• ${idx.key} (${idx.type}) on [${idx.attributes.join(', ')}]`).join('\n')} To create recommended indexes, use the create_index tool with the suggested parameters.` } ] }; } private extractFieldsFromQuery(query: string): string[] { // Simple pattern extraction - look for field names in common query patterns const patterns = [ /Query\.equal\(['"]([^'"]+)['"]/g, /Query\.search\(['"]([^'"]+)['"]/g, /Query\.orderBy\(['"]([^'"]+)['"]/g, /where\s+(\w+)\s*=/gi, /order\s+by\s+(\w+)/gi, /group\s+by\s+(\w+)/gi ]; const fields = new Set(); patterns.forEach(pattern => { let match; while ((match = pattern.exec(query)) !== null) { fields.add(match[1]); } }); return Array.from(fields); } private async validateDocument(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, documentData } = args; // Get collection schema const attributes = await this.databases.listAttributes(databaseId, collectionId); const attributeMap = new Map(attributes.attributes.map((attr: any) => [attr.key, attr])); const errors: string[] = []; const warnings: string[] = []; const valid = true; // Check required fields attributes.attributes.forEach((attr: any) => { if (attr.required && !(attr.key in documentData)) { errors.push(`Missing required field: ${attr.key} (${attr.type})`); } }); // Check field types and constraints Object.keys(documentData).forEach(key => { const attr = attributeMap.get(key); const value = documentData[key]; if (!attr) { warnings.push(`Unknown field: ${key} (not in collection schema)`); return; } // Type validation switch (attr.type) { case 'string': if (typeof value !== 'string') { errors.push(`Field ${key}: expected string, got ${typeof value}`); } else if (attr.size && value.length > attr.size) { errors.push(`Field ${key}: string too long (${value.length} > ${attr.size})`); } break; case 'integer': if (!Number.isInteger(value)) { errors.push(`Field ${key}: expected integer, got ${typeof value}`); } break; case 'boolean': if (typeof value !== 'boolean') { errors.push(`Field ${key}: expected boolean, got ${typeof value}`); } break; case 'email': if (typeof value !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { errors.push(`Field ${key}: invalid email format`); } break; case 'datetime': if (isNaN(Date.parse(value))) { errors.push(`Field ${key}: invalid datetime format`); } break; } }); const isValid = errors.length === 0; return { content: [ { type: "text", text: `Document Validation Result: Status: ${isValid ? '✅ VALID' : '❌ INVALID'} ${errors.length > 0 ? `Errors (${errors.length}): ${errors.map(e => `• ${e}`).join('\n')} ` : ''}${warnings.length > 0 ? `Warnings (${warnings.length}): ${warnings.map(w => `• ${w}`).join('\n')} ` : ''}Document Data: ${JSON.stringify(documentData, null, 2)} Collection Schema: ${attributes.attributes.map((attr: any) => `• ${attr.key} (${attr.type}) - Required: ${attr.required}${attr.size ? `, Max size: ${attr.size}` : ''}` ).join('\n')}` } ] }; } // Helper method for createDocument validation private async validateDocumentData(databaseId: string, collectionId: string, documentData: any) { if (!this.databases) throw new Error("Databases not initialized"); // Get collection schema const attributes = await this.databases.listAttributes(databaseId, collectionId); const attributeMap = new Map(attributes.attributes.map((attr: any) => [attr.key, attr])); const errors: string[] = []; // Check required fields attributes.attributes.forEach((attr: any) => { if (attr.required && !(attr.key in documentData)) { errors.push(`Missing required field: ${attr.key} (${attr.type})`); } }); // Check field types and constraints Object.keys(documentData).forEach(key => { const attr = attributeMap.get(key); const value = documentData[key]; if (!attr) { errors.push(`Unknown field: ${key} (not in collection schema)`); return; } // Type validation switch (attr.type) { case 'string': if (typeof value !== 'string') { errors.push(`Field ${key}: expected string, got ${typeof value}`); } else if (attr.size && value.length > attr.size) { errors.push(`Field ${key}: string too long (${value.length} > ${attr.size})`); } break; case 'integer': if (!Number.isInteger(value)) { errors.push(`Field ${key}: expected integer, got ${typeof value}`); } break; case 'boolean': if (typeof value !== 'boolean') { errors.push(`Field ${key}: expected boolean, got ${typeof value}`); } break; case 'email': if (typeof value !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { errors.push(`Field ${key}: invalid email format`); } break; case 'datetime': if (isNaN(Date.parse(value))) { errors.push(`Field ${key}: invalid datetime format`); } break; } }); return { isValid: errors.length === 0, errors: errors }; } private async schemaMigration(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, newSchema, migrationStrategy = 'safe' } = args; // Get current schema const currentAttributes = await this.databases.listAttributes(databaseId, collectionId); const currentAttrMap = new Map(currentAttributes.attributes.map((attr: any) => [attr.key, attr])); const migration = { toAdd: [] as any[], toRemove: [] as any[], toModify: [] as any[], warnings: [] as string[] }; // Analyze differences newSchema.forEach((newAttr: any) => { const current = currentAttrMap.get(newAttr.key); if (!current) { migration.toAdd.push(newAttr); } else if (current.type !== newAttr.type) { migration.toModify.push({ key: newAttr.key, currentType: current.type, newType: newAttr.type, action: 'type_change' }); migration.warnings.push(`Type change for ${newAttr.key}: ${current.type} → ${newAttr.type} may cause data loss`); } }); // Find attributes to remove currentAttributes.attributes.forEach((currentAttr: any) => { if (!newSchema.find((newAttr: any) => newAttr.key === currentAttr.key)) { migration.toRemove.push(currentAttr); if (migrationStrategy === 'safe') { migration.warnings.push(`Attribute ${currentAttr.key} will be removed - this cannot be undone`); } } }); let migrationResult = ''; if (migrationStrategy === 'safe' && (migration.toRemove.length > 0 || migration.toModify.length > 0)) { return { content: [ { type: "text", text: `Schema Migration Analysis (Safe Mode): ⚠️ MIGRATION BLOCKED - Potentially destructive changes detected: To Add (${migration.toAdd.length}): ${migration.toAdd.map(attr => `• ${attr.key} (${attr.type})`).join('\n')} To Remove (${migration.toRemove.length}): ${migration.toRemove.map(attr => `• ${attr.key} (${attr.type})`).join('\n')} To Modify (${migration.toModify.length}): ${migration.toModify.map(attr => `• ${attr.key}: ${attr.currentType} → ${attr.newType}`).join('\n')} Warnings: ${migration.warnings.map(w => `• ${w}`).join('\n')} To proceed with potentially destructive changes, use migrationStrategy: "aggressive"` } ] }; } // Execute migration const results = []; // Add new attributes for (const attr of migration.toAdd) { try { switch (attr.type) { case 'string': await this.databases.createStringAttribute( databaseId, collectionId, attr.key, attr.size || 255, attr.required || false ); break; case 'integer': await this.databases.createIntegerAttribute( databaseId, collectionId, attr.key, attr.required || false ); break; case 'boolean': await this.databases.createBooleanAttribute( databaseId, collectionId, attr.key, attr.required || false ); break; case 'email': await this.databases.createEmailAttribute( databaseId, collectionId, attr.key, attr.required || false ); break; case 'datetime': await this.databases.createDatetimeAttribute( databaseId, collectionId, attr.key, attr.required || false ); break; } results.push(`✅ Added ${attr.key} (${attr.type})`); } catch (error) { results.push(`❌ Failed to add ${attr.key}: ${error}`); } } // Remove attributes (aggressive mode only) if (migrationStrategy === 'aggressive') { for (const attr of migration.toRemove) { try { await this.databases.deleteAttribute(databaseId, collectionId, attr.key); results.push(`✅ Removed ${attr.key}`); } catch (error) { results.push(`❌ Failed to remove ${attr.key}: ${error}`); } } } return { content: [ { type: "text", text: `Schema Migration Completed (${migrationStrategy} mode): Migration Results: ${results.join('\n')} ${migration.warnings.length > 0 ? `\nWarnings: ${migration.warnings.map(w => `• ${w}`).join('\n')}` : ''} Summary: • Added: ${migration.toAdd.length} attributes • Removed: ${migrationStrategy === 'aggressive' ? migration.toRemove.length : 0} attributes • Modified: ${migration.toModify.length} attributes` } ] }; } // Data Analysis & Insights private async analyzeCollection(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, analysisType = 'basic' } = args; try { // Get collection schema const attributes = await this.databases.listAttributes(databaseId, collectionId); const indexes = await this.databases.listIndexes(databaseId, collectionId); // Get sample of documents for analysis const documents = await this.databases.listDocuments( databaseId, collectionId, [Query.limit(1000)] ); const analysis = this.performCollectionAnalysis(documents.documents, attributes.attributes, analysisType); return { content: [ { type: "text", text: `Collection Analysis: ${collectionId} 📊 Basic Statistics: • Total documents: ${documents.total} • Attributes: ${attributes.total} • Indexes: ${indexes.total} • Sample size: ${documents.documents.length} 🏗️ Schema Analysis: ${attributes.attributes.map((attr: any) => `• ${attr.key} (${attr.type}) - Required: ${attr.required}${attr.size ? `, Size: ${attr.size}` : ''}` ).join('\n')} 📈 Data Distribution: ${JSON.stringify(analysis.distribution, null, 2)} ${analysisType === 'detailed' || analysisType === 'statistical' ? ` 🔍 Detailed Insights: ${JSON.stringify(analysis.insights, null, 2)}` : ''} ${analysisType === 'statistical' ? ` 📊 Statistical Analysis: ${JSON.stringify(analysis.statistics, null, 2)}` : ''} 💡 Recommendations: ${analysis.recommendations.map((rec: string) => `• ${rec}`).join('\n')}` } ] }; } catch (error) { return { content: [ { type: "text", text: `Collection Analysis Failed: Error: ${error} Please ensure: • Collection exists and is accessible • You have read permissions • Collection is not empty` } ] }; } } private performCollectionAnalysis(documents: any[], attributes: any[], analysisType: string): any { const analysis: any = { distribution: {}, insights: {}, statistics: {}, recommendations: [] }; // Basic distribution analysis attributes.forEach(attr => { const values = documents.map(doc => doc[attr.key]).filter(val => val !== null && val !== undefined); const uniqueValues = [...new Set(values)]; analysis.distribution[attr.key] = { totalValues: values.length, uniqueValues: uniqueValues.length, nullPercentage: ((documents.length - values.length) / documents.length * 100).toFixed(1) + '%', mostCommonValue: this.getMostCommonValue(values) }; // Type-specific analysis if (attr.type === 'string') { const lengths = values.map(val => String(val).length); analysis.distribution[attr.key].avgLength = lengths.reduce((sum, len) => sum + len, 0) / lengths.length; analysis.distribution[attr.key].maxLength = Math.max(...lengths); } else if (attr.type === 'integer') { const numbers = values.filter(val => typeof val === 'number'); if (numbers.length > 0) { analysis.distribution[attr.key].min = Math.min(...numbers); analysis.distribution[attr.key].max = Math.max(...numbers); analysis.distribution[attr.key].avg = numbers.reduce((sum, num) => sum + num, 0) / numbers.length; } } }); // Detailed insights if (analysisType === 'detailed' || analysisType === 'statistical') { analysis.insights = { dataQuality: this.assessDataQuality(documents, attributes), patterns: this.findDataPatterns(documents), relationships: this.detectRelationships(documents, attributes) }; } // Statistical analysis if (analysisType === 'statistical') { analysis.statistics = this.calculateStatistics(documents, attributes); } // Generate recommendations analysis.recommendations = this.generateRecommendations(documents, attributes, analysis); return analysis; } private getMostCommonValue(values: any[]): any { const frequency: { [key: string]: number } = {}; values.forEach(val => { const key = String(val); frequency[key] = (frequency[key] || 0) + 1; }); let mostCommon = null; let maxCount = 0; Object.keys(frequency).forEach(key => { if (frequency[key] > maxCount) { maxCount = frequency[key]; mostCommon = key; } }); return { value: mostCommon, frequency: maxCount }; } private assessDataQuality(documents: any[], attributes: any[]): any { return { completeness: attributes.map(attr => ({ field: attr.key, completeness: ((documents.filter(doc => doc[attr.key] !== null && doc[attr.key] !== undefined).length / documents.length) * 100).toFixed(1) + '%' })), duplicates: this.findDuplicateDocuments(documents) }; } private findDataPatterns(documents: any[]): any { return { commonFields: this.findCommonFieldPatterns(documents), temporalPatterns: this.findTemporalPatterns(documents) }; } private detectRelationships(documents: any[], attributes: any[]): any { return { foreignKeys: attributes.filter(attr => attr.key.endsWith('Id') || attr.key.endsWith('_id')), potentialRelations: this.findPotentialRelations(documents, attributes) }; } private calculateStatistics(documents: any[], attributes: any[]): any { const stats: any = {}; attributes.forEach(attr => { if (attr.type === 'integer') { const values = documents.map(doc => doc[attr.key]).filter(val => typeof val === 'number'); if (values.length > 0) { const sorted = values.sort((a, b) => a - b); stats[attr.key] = { mean: values.reduce((sum, val) => sum + val, 0) / values.length, median: sorted[Math.floor(sorted.length / 2)], mode: this.getMostCommonValue(values).value, standardDeviation: this.calculateStandardDeviation(values) }; } } }); return stats; } private calculateStandardDeviation(values: number[]): number { const mean = values.reduce((sum, val) => sum + val, 0) / values.length; const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length; return Math.sqrt(variance); } private generateRecommendations(documents: any[], attributes: any[], analysis: any): string[] { const recommendations = []; // Index recommendations attributes.forEach(attr => { if (attr.key.match(/(id|email|status|type)/i) && analysis.distribution[attr.key]?.uniqueValues > 10) { recommendations.push(`Consider adding an index on '${attr.key}' for better query performance`); } }); // Data quality recommendations Object.keys(analysis.distribution).forEach(field => { const dist = analysis.distribution[field]; if (parseFloat(dist.nullPercentage) > 50) { recommendations.push(`Field '${field}' has high null percentage (${dist.nullPercentage}) - consider making it optional or providing defaults`); } }); // Schema recommendations if (attributes.length > 20) { recommendations.push('Consider splitting this collection into smaller, more focused collections'); } return recommendations; } private findDuplicateDocuments(documents: any[]): any { // Simple duplicate detection based on multiple fields const seen = new Map(); const duplicates: any[] = []; documents.forEach((doc, index) => { const key = JSON.stringify(Object.keys(doc).sort().map(k => doc[k])); if (seen.has(key)) { duplicates.push({ index, duplicateOf: seen.get(key) }); } else { seen.set(key, index); } }); return { count: duplicates.length, examples: duplicates.slice(0, 5) }; } private findCommonFieldPatterns(documents: any[]): any { const patterns: { [key: string]: number } = {}; documents.forEach(doc => { const fields = Object.keys(doc).sort().join(','); patterns[fields] = (patterns[fields] || 0) + 1; }); return Object.keys(patterns).map(pattern => ({ pattern, frequency: patterns[pattern] })).sort((a, b) => b.frequency - a.frequency).slice(0, 5); } private findTemporalPatterns(documents: any[]): any { const createdDates = documents .map(doc => doc.$createdAt) .filter(date => date) .map(date => new Date(date)); if (createdDates.length === 0) return null; const dateGroups: { [key: string]: number } = {}; createdDates.forEach(date => { const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; dateGroups[monthKey] = (dateGroups[monthKey] || 0) + 1; }); return { creationPattern: dateGroups, peakMonth: Object.keys(dateGroups).reduce((a, b) => dateGroups[a] > dateGroups[b] ? a : b) }; } private findPotentialRelations(documents: any[], attributes: any[]): any { return attributes.filter(attr => attr.key.endsWith('Id') || attr.key.endsWith('_id') || attr.key.match(/(user|author|category|parent)/i) ).map(attr => ({ field: attr.key, possibleRelation: attr.key.replace(/Id$|_id$/i, ''), uniqueValues: [...new Set(documents.map(doc => doc[attr.key]))].length })); } private async detectDuplicates(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, fields, threshold = 0.9 } = args; try { const documents = await this.databases.listDocuments( databaseId, collectionId, [Query.limit(5000)] ); const duplicates = this.findDuplicates(documents.documents, fields, threshold); return { content: [ { type: "text", text: `Duplicate Detection Results: Collection: ${collectionId} Fields analyzed: ${fields.join(', ')} Similarity threshold: ${threshold} Documents scanned: ${documents.documents.length} 🔍 Duplicate Groups Found: ${duplicates.groups.length} ${duplicates.groups.slice(0, 10).map((group: any, index: number) => `Group ${index + 1} (${group.documents.length} duplicates): Similarity: ${(group.similarity * 100).toFixed(1)}% Documents: ${group.documents.map((doc: any) => doc.$id).join(', ')} Common values: ${JSON.stringify(group.commonValues, null, 2)}` ).join('\n\n')} 📊 Summary: • Total potential duplicates: ${duplicates.totalDuplicates} • Exact matches: ${duplicates.exactMatches} • Similar matches: ${duplicates.similarMatches} • Clean documents: ${documents.documents.length - duplicates.totalDuplicates} 💡 Actions: • Review duplicate groups manually • Consider merging exact matches • Add unique constraints to prevent future duplicates` } ] }; } catch (error) { return { content: [ { type: "text", text: `Duplicate Detection Failed: Error: ${error} Please ensure: • Collection exists and is accessible • Specified fields exist in the collection • You have read permissions` } ] }; } } private findDuplicates(documents: any[], fields: string[], threshold: number): any { const groups = []; const processed = new Set(); let totalDuplicates = 0; let exactMatches = 0; let similarMatches = 0; for (let i = 0; i < documents.length; i++) { if (processed.has(i)) continue; const currentDoc = documents[i]; const duplicateGroup = [currentDoc]; processed.add(i); for (let j = i + 1; j < documents.length; j++) { if (processed.has(j)) continue; const compareDoc = documents[j]; const similarity = this.calculateSimilarity(currentDoc, compareDoc, fields); if (similarity >= threshold) { duplicateGroup.push(compareDoc); processed.add(j); if (similarity === 1.0) exactMatches++; else similarMatches++; } } if (duplicateGroup.length > 1) { const commonValues: any = {}; fields.forEach(field => { const values = duplicateGroup.map(doc => doc[field]); const uniqueValues = [...new Set(values)]; if (uniqueValues.length === 1) { commonValues[field] = uniqueValues[0]; } }); groups.push({ documents: duplicateGroup, similarity: this.calculateGroupSimilarity(duplicateGroup, fields), commonValues }); totalDuplicates += duplicateGroup.length; } } return { groups, totalDuplicates, exactMatches, similarMatches }; } private calculateSimilarity(doc1: any, doc2: any, fields: string[]): number { let matches = 0; let total = 0; fields.forEach(field => { total++; const val1 = String(doc1[field] || '').toLowerCase().trim(); const val2 = String(doc2[field] || '').toLowerCase().trim(); if (val1 === val2) { matches++; } else if (val1 && val2) { // Fuzzy matching for strings const similarity = this.stringSimilarity(val1, val2); matches += similarity; } }); return total > 0 ? matches / total : 0; } private stringSimilarity(str1: string, str2: string): number { const longer = str1.length > str2.length ? str1 : str2; const shorter = str1.length > str2.length ? str2 : str1; if (longer.length === 0) return 1.0; const editDistance = this.levenshteinDistance(longer, shorter); return (longer.length - editDistance) / longer.length; } private levenshteinDistance(str1: string, str2: string): number { const matrix = []; for (let i = 0; i <= str2.length; i++) { matrix[i] = [i]; } for (let j = 0; j <= str1.length; j++) { matrix[0][j] = j; } for (let i = 1; i <= str2.length; i++) { for (let j = 1; j <= str1.length; j++) { if (str2.charAt(i - 1) === str1.charAt(j - 1)) { matrix[i][j] = matrix[i - 1][j - 1]; } else { matrix[i][j] = Math.min( matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1 ); } } } return matrix[str2.length][str1.length]; } private calculateGroupSimilarity(documents: any[], fields: string[]): number { let totalSimilarity = 0; let comparisons = 0; for (let i = 0; i < documents.length; i++) { for (let j = i + 1; j < documents.length; j++) { totalSimilarity += this.calculateSimilarity(documents[i], documents[j], fields); comparisons++; } } return comparisons > 0 ? totalSimilarity / comparisons : 0; } private async dataQualityCheck(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, checkType = 'all' } = args; try { const attributes = await this.databases.listAttributes(databaseId, collectionId); const documents = await this.databases.listDocuments( databaseId, collectionId, [Query.limit(2000)] ); const qualityReport = this.performDataQualityCheck(documents.documents, attributes.attributes, checkType); return { content: [ { type: "text", text: `Data Quality Report: ${collectionId} 📊 Overall Quality Score: ${qualityReport.overallScore}/100 ${checkType === 'all' || checkType === 'completeness' ? ` 🔍 Completeness Check: ${qualityReport.completeness.map((item: any) => `• ${item.field}: ${item.completeness}% complete (${item.missingCount} missing)` ).join('\n')}` : ''} ${checkType === 'all' || checkType === 'validity' ? ` ✅ Validity Check: ${qualityReport.validity.map((item: any) => `• ${item.field}: ${item.validCount}/${item.totalCount} valid (${item.errorTypes.join(', ') || 'No errors'})` ).join('\n')}` : ''} ${checkType === 'all' || checkType === 'consistency' ? ` 🔄 Consistency Check: ${qualityReport.consistency.map((item: any) => `• ${item.field}: ${item.consistencyScore}% consistent (${item.issues.join(', ') || 'No issues'})` ).join('\n')}` : ''} 🚨 Issues Found (${qualityReport.totalIssues}): ${qualityReport.issues.slice(0, 10).map((issue: any) => `• ${issue.severity.toUpperCase()}: ${issue.field} - ${issue.description}` ).join('\n')} 💡 Recommendations: ${qualityReport.recommendations.map((rec: string) => `• ${rec}`).join('\n')} 📈 Quality Trends: • Fields with >90% quality: ${qualityReport.highQualityFields} • Fields needing attention: ${qualityReport.lowQualityFields.length} • Data integrity score: ${qualityReport.integrityScore}/100` } ] }; } catch (error) { return { content: [ { type: "text", text: `Data Quality Check Failed: Error: ${error} Please ensure: • Collection exists and is accessible • You have read permissions • Collection contains data to analyze` } ] }; } } private performDataQualityCheck(documents: any[], attributes: any[], checkType: string): any { const report: any = { completeness: [], validity: [], consistency: [], issues: [], recommendations: [], totalIssues: 0, overallScore: 0, highQualityFields: 0, lowQualityFields: [], integrityScore: 0 }; // Completeness check if (checkType === 'all' || checkType === 'completeness') { attributes.forEach(attr => { const missingCount = documents.filter(doc => doc[attr.key] === null || doc[attr.key] === undefined || doc[attr.key] === '' ).length; const completeness = ((documents.length - missingCount) / documents.length) * 100; report.completeness.push({ field: attr.key, completeness: completeness.toFixed(1), missingCount, totalCount: documents.length }); if (missingCount > 0 && attr.required) { report.issues.push({ severity: 'high', field: attr.key, description: `Required field has ${missingCount} missing values` }); } }); } // Validity check if (checkType === 'all' || checkType === 'validity') { attributes.forEach(attr => { let validCount = 0; const errorTypes: string[] = []; documents.forEach(doc => { const value = doc[attr.key]; if (value === null || value === undefined) return; let isValid = true; switch (attr.type) { case 'email': if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value))) { isValid = false; if (!errorTypes.includes('invalid_email')) errorTypes.push('invalid_email'); } break; case 'integer': if (!Number.isInteger(value)) { isValid = false; if (!errorTypes.includes('not_integer')) errorTypes.push('not_integer'); } break; case 'boolean': if (typeof value !== 'boolean') { isValid = false; if (!errorTypes.includes('not_boolean')) errorTypes.push('not_boolean'); } break; case 'datetime': if (isNaN(Date.parse(value))) { isValid = false; if (!errorTypes.includes('invalid_date')) errorTypes.push('invalid_date'); } break; case 'string': if (attr.size && String(value).length > attr.size) { isValid = false; if (!errorTypes.includes('exceeds_length')) errorTypes.push('exceeds_length'); } break; } if (isValid) validCount++; }); report.validity.push({ field: attr.key, validCount, totalCount: documents.length, errorTypes }); if (errorTypes.length > 0) { report.issues.push({ severity: 'medium', field: attr.key, description: `Validation errors: ${errorTypes.join(', ')}` }); } }); } // Consistency check if (checkType === 'all' || checkType === 'consistency') { attributes.forEach(attr => { const values = documents.map(doc => doc[attr.key]).filter(val => val !== null && val !== undefined); const issues = []; let consistencyScore = 100; if (attr.type === 'string') { // Check for case inconsistencies const caseVariations = new Set(values.map(val => String(val).toLowerCase())); const originalVariations = new Set(values.map(val => String(val))); if (originalVariations.size > caseVariations.size) { issues.push('case_inconsistency'); consistencyScore -= 20; } // Check for whitespace inconsistencies const trimmedValues = values.map(val => String(val).trim()); if (trimmedValues.some((val, idx) => val !== values[idx])) { issues.push('whitespace_issues'); consistencyScore -= 15; } } report.consistency.push({ field: attr.key, consistencyScore: Math.max(0, consistencyScore), issues }); if (issues.length > 0) { report.issues.push({ severity: 'low', field: attr.key, description: `Consistency issues: ${issues.join(', ')}` }); } }); } // Calculate scores report.totalIssues = report.issues.length; const completenessScores = report.completeness.map((c: any) => parseFloat(c.completeness)); const validityScores = report.validity.map((v: any) => (v.validCount / v.totalCount) * 100); const consistencyScores = report.consistency.map((c: any) => c.consistencyScore); const allScores = [...completenessScores, ...validityScores, ...consistencyScores]; report.overallScore = allScores.length > 0 ? Math.round(allScores.reduce((sum, score) => sum + score, 0) / allScores.length) : 0; report.highQualityFields = allScores.filter(score => score > 90).length; report.lowQualityFields = attributes.filter((attr, idx) => (completenessScores[idx] || 0) < 70 || (validityScores[idx] || 0) < 70 || (consistencyScores[idx] || 0) < 70 ); report.integrityScore = Math.max(0, 100 - (report.totalIssues * 5)); // Generate recommendations if (report.lowQualityFields.length > 0) { report.recommendations.push(`Focus on improving ${report.lowQualityFields.length} low-quality fields`); } if (report.totalIssues > 10) { report.recommendations.push('Consider implementing data validation at input level'); } if (report.overallScore < 80) { report.recommendations.push('Overall data quality needs improvement - consider data cleansing'); } return report; } private async usageStats(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, timeRange = '7d' } = args; try { // Note: Appwrite doesn't provide usage analytics, so we'll simulate based on available data const stats = await this.generateUsageStats(databaseId, collectionId, timeRange); return { content: [ { type: "text", text: `Usage Statistics Report: 📊 Overview (Last ${timeRange}): • Database: ${databaseId} ${collectionId ? `• Collection: ${collectionId}` : '• All Collections'} 📈 Document Activity: ${JSON.stringify(stats.documentActivity, null, 2)} 🔍 Query Patterns (Estimated): ${JSON.stringify(stats.queryPatterns, null, 2)} 📁 Collection Usage: ${JSON.stringify(stats.collectionUsage, null, 2)} ⚡ Performance Insights: ${JSON.stringify(stats.performance, null, 2)} 💡 Optimization Suggestions: ${stats.suggestions.map((suggestion: string) => `• ${suggestion}`).join('\n')} ⚠️ Note: Usage statistics are estimated based on available data. For accurate analytics, consider implementing custom tracking or using Appwrite's built-in analytics when available.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Usage Statistics Failed: Error: ${error} Note: Appwrite doesn't provide detailed usage analytics through the API. This tool provides estimated statistics based on available document data. For accurate usage tracking, consider: • Implementing custom analytics in your application • Using Appwrite Console analytics (when available) • Adding logging to your application queries` } ] }; } } private async generateUsageStats(databaseId: string, collectionId?: string, timeRange: string = '7d'): Promise { const stats: any = { documentActivity: {}, queryPatterns: {}, collectionUsage: {}, performance: {}, suggestions: [] }; // Get time range const days = parseInt(timeRange.replace('d', '')); const startDate = new Date(); startDate.setDate(startDate.getDate() - days); if (collectionId) { // Single collection stats const documents = await this.databases!.listDocuments(databaseId, collectionId, [Query.limit(1000)]); const attributes = await this.databases!.listAttributes(databaseId, collectionId); const indexes = await this.databases!.listIndexes(databaseId, collectionId); // Document activity (based on creation dates) const recentDocs = documents.documents.filter(doc => new Date(doc.$createdAt) >= startDate ); stats.documentActivity = { totalDocuments: documents.total, recentDocuments: recentDocs.length, creationRate: (recentDocs.length / days).toFixed(1) + ' docs/day', lastActivity: documents.documents.length > 0 ? documents.documents[0].$updatedAt : 'N/A' }; // Estimated query patterns stats.queryPatterns = { estimatedReads: Math.floor(documents.total * 0.1 * days), // Estimate estimatedWrites: recentDocs.length, popularFields: attributes.attributes .filter((attr: any) => attr.key.match(/(id|name|status|email)/i)) .map((attr: any) => attr.key), indexUtilization: (indexes.total / Math.max(1, attributes.total) * 100).toFixed(1) + '%' }; // Performance insights stats.performance = { attributeCount: attributes.total, indexCount: indexes.total, avgDocumentSize: this.estimateDocumentSize(documents.documents), recommendedIndexes: indexes.total < 3 ? 'Consider adding more indexes' : 'Good index coverage' }; } else { // All collections stats const databases = await this.databases!.list(); const collections = await this.databases!.listCollections(databaseId); stats.collectionUsage = {}; let totalDocs = 0; for (const collection of collections.collections.slice(0, 5)) { // Limit for performance try { const docs = await this.databases!.listDocuments(databaseId, collection.$id, [Query.limit(100)]); const recentDocs = docs.documents.filter(doc => new Date(doc.$createdAt) >= startDate ); stats.collectionUsage[collection.name] = { totalDocuments: docs.total, recentActivity: recentDocs.length, lastUpdated: docs.documents.length > 0 ? docs.documents[0].$updatedAt : 'N/A' }; totalDocs += docs.total; } catch (error) { // Skip collections we can't access } } stats.documentActivity = { totalCollections: collections.total, totalDocuments: totalDocs, averageDocsPerCollection: (totalDocs / collections.total).toFixed(1), timeRange: timeRange }; } // Generate suggestions if (collectionId) { const docs = await this.databases!.listDocuments(databaseId, collectionId, [Query.limit(100)]); const indexes = await this.databases!.listIndexes(databaseId, collectionId); if (indexes.total === 0 && docs.total > 100) { stats.suggestions.push('Add indexes to improve query performance on large collection'); } if (docs.total > 10000) { stats.suggestions.push('Consider pagination for large result sets'); } if (stats.queryPatterns?.indexUtilization && parseFloat(stats.queryPatterns.indexUtilization) < 50) { stats.suggestions.push('Low index utilization - review query patterns and add targeted indexes'); } } return stats; } private estimateDocumentSize(documents: any[]): string { if (documents.length === 0) return 'N/A'; const sample = documents.slice(0, 10); const totalSize = sample.reduce((sum, doc) => { return sum + JSON.stringify(doc).length; }, 0); const avgSize = totalSize / sample.length; return avgSize < 1024 ? Math.round(avgSize) + ' bytes' : (avgSize / 1024).toFixed(1) + ' KB'; } // Session Management private async createSession(args: any) { if (!this.account) throw new Error("Account service not initialized"); const { email, password } = args; const session = await this.account.createEmailPasswordSession(email, password); return { content: [ { type: "text", text: `Session created successfully:\n- Session ID: ${session.$id}\n- User ID: ${session.userId}\n- Created: ${session.$createdAt}\n- Expires: ${session.expire}` } ] }; } private async deleteSession(args: any) { if (!this.account) throw new Error("Account service not initialized"); const { sessionId } = args; try { await this.account.deleteSession(sessionId); return { content: [ { type: "text", text: `Session ${sessionId} deleted successfully` } ] }; } catch (error: any) { if (error.message.includes('missing scope (account)')) { throw new Error("API key missing 'account' scope. Please add 'account.read' and 'account.write' permissions to your API key in the Appwrite Console."); } throw error; } } private async listSessions(args: any) { if (!this.users) throw new Error("Users service not initialized"); const { userId } = args; const sessions = await this.users.listSessions(userId); const sessionList = sessions.sessions.map((session: any) => `- ${session.$id} - Created: ${session.$createdAt} - Expires: ${session.expire}` ).join('\n'); return { content: [ { type: "text", text: `Sessions for user ${userId} (${sessions.total}):\n${sessionList}` } ] }; } // Function Execution private async executeFunction(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, data, async } = args; const execution = await this.functions.createExecution(functionId, data, async); return { content: [ { type: "text", text: `Function execution ${async ? 'started' : 'completed'}:\n- Execution ID: ${execution.$id}\n- Function ID: ${execution.functionId}\n- Status: ${execution.status}\n- Response: ${execution.responseBody || 'No response'}\n- Duration: ${execution.duration}ms` } ] }; } private async listExecutions(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, limit = 25 } = args; const executions = await this.functions.listExecutions(functionId, [Query.limit(limit)]); const executionList = executions.executions.map((exec: any) => `- ${exec.$id} - Status: ${exec.status} - Duration: ${exec.duration}ms - Created: ${exec.$createdAt}` ).join('\n'); return { content: [ { type: "text", text: `Function executions for ${functionId} (${executions.total}):\n${executionList}` } ] }; } private async getExecution(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); const { functionId, executionId } = args; const execution = await this.functions.getExecution(functionId, executionId); return { content: [ { type: "text", text: `Execution Details:\n${JSON.stringify(execution, null, 2)}` } ] }; } // Health Monitoring private async getHealth() { if (!this.health) throw new Error("Health service not initialized"); const health = await this.health.get(); return { content: [ { type: "text", text: `Health Status: ${health.status}\n- Ping: ${health.ping}ms` } ] }; } private async getHealthDb() { if (!this.health) throw new Error("Health service not initialized"); const health = await this.health.getDB() as any; return { content: [ { type: "text", text: `Database Health: ${health.status || 'OK'}\n- Ping: ${health.ping || health.duration || 'N/A'}ms` } ] }; } private async getHealthStorage() { if (!this.health) throw new Error("Health service not initialized"); const health = await this.health.getStorage(); return { content: [ { type: "text", text: `Storage Health: ${health.status}\n- Ping: ${health.ping}ms` } ] }; } // Bulk Operations private async bulkCreateUsers(args: any) { if (!this.users) throw new Error("Users service not initialized"); const { users } = args; const results = []; const errors = []; for (const user of users) { try { const { userId, email, phone, password, name } = user; const uid = userId || ID.unique(); const createdUser = await this.users.create(uid, email, phone, password, name); results.push(`✅ ${createdUser.email} (${createdUser.$id})`); } catch (error) { errors.push(`❌ ${user.email}: ${error}`); } } return { content: [ { type: "text", text: `Bulk User Creation Results:\n\nSuccessful (${results.length}):\n${results.join('\n')}\n\nFailed (${errors.length}):\n${errors.join('\n')}` } ] }; } private async bulkUpdateUsers(args: any) { if (!this.users) throw new Error("Users service not initialized"); const { updates } = args; const results = []; const errors = []; for (const update of updates) { try { const { userId, email, name, password } = update; if (email) await this.users.updateEmail(userId, email); if (name) await this.users.updateName(userId, name); if (password) await this.users.updatePassword(userId, password); results.push(`✅ User ${userId} updated`); } catch (error) { errors.push(`❌ User ${update.userId}: ${error}`); } } return { content: [ { type: "text", text: `Bulk User Update Results:\n\nSuccessful (${results.length}):\n${results.join('\n')}\n\nFailed (${errors.length}):\n${errors.join('\n')}` } ] }; } private async bulkDeleteUsers(args: any) { if (!this.users) throw new Error("Users service not initialized"); const { userIds } = args; const results = []; const errors = []; for (const userId of userIds) { try { await this.users.delete(userId); results.push(`✅ User ${userId} deleted`); } catch (error) { errors.push(`❌ User ${userId}: ${error}`); } } return { content: [ { type: "text", text: `Bulk User Deletion Results:\n\nSuccessful (${results.length}):\n${results.join('\n')}\n\nFailed (${errors.length}):\n${errors.join('\n')}` } ] }; } private async bulkCreateDocuments(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, documents } = args; const results = []; const errors = []; for (const doc of documents) { try { const { documentId, data, permissions } = doc; const docId = documentId || ID.unique(); const createdDoc = await this.databases.createDocument(databaseId, collectionId, docId, data, permissions); results.push(`✅ Document ${createdDoc.$id} created`); } catch (error) { errors.push(`❌ Document: ${error}`); } } return { content: [ { type: "text", text: `Bulk Document Creation Results:\n\nSuccessful (${results.length}):\n${results.join('\n')}\n\nFailed (${errors.length}):\n${errors.join('\n')}` } ] }; } private async bulkUpdateDocuments(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, updates } = args; const results = []; const errors = []; for (const update of updates) { try { const { documentId, data, permissions } = update; await this.databases.updateDocument(databaseId, collectionId, documentId, data, permissions); results.push(`✅ Document ${documentId} updated`); } catch (error) { errors.push(`❌ Document ${update.documentId}: ${error}`); } } return { content: [ { type: "text", text: `Bulk Document Update Results:\n\nSuccessful (${results.length}):\n${results.join('\n')}\n\nFailed (${errors.length}):\n${errors.join('\n')}` } ] }; } private async bulkDeleteDocuments(args: any) { if (!this.databases) throw new Error("Databases not initialized"); const { databaseId, collectionId, documentIds } = args; const results = []; const errors = []; for (const documentId of documentIds) { try { await this.databases.deleteDocument(databaseId, collectionId, documentId); results.push(`✅ Document ${documentId} deleted`); } catch (error) { errors.push(`❌ Document ${documentId}: ${error}`); } } return { content: [ { type: "text", text: `Bulk Document Deletion Results:\n\nSuccessful (${results.length}):\n${results.join('\n')}\n\nFailed (${errors.length}):\n${errors.join('\n')}` } ] }; } async run(): Promise { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error("Appwrite MCP server running on stdio"); } } const server = new AppwriteMCPServer(); server.run().catch(console.error);