diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..c8f31a9 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run:*)", + "Bash(npm help)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b2521b4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,94 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +### Build and Development +```bash +# Build TypeScript to JavaScript +npm run build + +# Watch mode for development +npm run dev + +# Run the built server +npm start +``` + +### Testing and Validation +Before committing changes, ensure the TypeScript compiles without errors: +```bash +npm run build +``` + +## Architecture Overview + +This is an Appwrite Model Context Protocol (MCP) server that provides a comprehensive interface between Claude and Appwrite services. The server is built in TypeScript and uses the official Appwrite Node.js SDK. + +### Key Components + +1. **MCP Server Core** (`src/index.ts`): + - Main server class `AppwriteMCPServer` that initializes all Appwrite service clients + - Implements 135 tools covering all major Appwrite services + - Uses environment variables for configuration (loaded from `.env` in current working directory) + +2. **Service Clients**: + - Databases, Users, Storage, Functions, Teams, Account, Health, Messaging, Locale, Avatars + - All clients are initialized with API key authentication + +3. **Tool Categories**: + - Database operations (create, list, delete databases) + - Collection management with attribute and index operations + - Document CRUD with query support + - User management including MFA, preferences, identities, and messaging targets + - Storage bucket and file operations + - Function management with deployments and variables + - Messaging service (providers, messages, topics, subscribers) + - Locale service for internationalization + - Avatar generation utilities + - Bulk operations for efficient batch processing + - Advanced features like schema auto-detection and data analysis + +### Configuration + +The server reads configuration from environment variables: +- `APPWRITE_PROJECT_ID`: Target Appwrite project +- `APPWRITE_API_ENDPOINT`: Appwrite API endpoint (cloud or self-hosted) +- `APPWRITE_API_KEY`: API key with necessary scopes + +The `.env` file is loaded from the current working directory where Claude Code is run, allowing project-specific configurations. + +### Tool Implementation Pattern + +Tools follow a consistent pattern: +1. Parse and validate input parameters +2. Initialize appropriate Appwrite service client +3. Execute the operation with error handling +4. Return formatted response with relevant data + +### Error Handling + +All tools implement comprehensive error handling: +- Configuration validation before operations +- Detailed error messages for debugging +- Graceful fallbacks for missing optional parameters + +## Development Guidelines + +1. **Adding New Tools**: Follow the existing pattern in `src/index.ts`, ensuring proper parameter validation and error handling. + +2. **Type Safety**: Leverage TypeScript's strict mode for all new code. The project uses ES2022 target with Node16 module resolution. + +3. **Environment Variables**: Never commit `.env` files. Use `.env.example` as a template for configuration. + +4. **Build Output**: Compiled JavaScript goes to `dist/` directory. Source maps are generated for debugging. + +## Integration Notes + +This server can be integrated with: +- Claude Code (CLI) via `claude mcp add` command +- Cursor IDE via settings.json configuration +- Continue.dev via config.json + +Each integration method supports different configuration approaches, with Claude Code using `.env` files and IDEs requiring inline environment variable configuration. \ No newline at end of file diff --git a/removed-tools-backup.ts b/removed-tools-backup.ts new file mode 100644 index 0000000..96a61e6 --- /dev/null +++ b/removed-tools-backup.ts @@ -0,0 +1,409 @@ +/** + * REMOVED TOOLS BACKUP + * + * This file contains all the tools that were removed from the main MCP server + * to reduce context usage. These tools can be restored if needed in the future. + * + * Removed on: 2025-09-05 + * Reason: Reduce MCP context usage from ~61,830 tokens to under 25,000 tokens + * + * Categories of removed tools: + * 1. Data Analysis & Intelligence Tools (8 tools) + * 2. Avatars Service (7 tools) + * 3. Locale Service (5 tools) + * 4. Advanced User Management (12 tools) + * 5. Messaging Targets (5 tools) + * 6. Health Monitoring (2 tools) - kept get_health only + * 7. Session Management (3 tools) + * 8. Function Execution (1 tool) + * + * Total removed: ~43 tools + */ + +// ===== DATA ANALYSIS & INTELLIGENCE TOOLS (8 tools) ===== + +// auto_detect_schema - Smart analysis feature +const autoDetectSchemaTool = { + name: "auto_detect_schema", + description: "Analyze sample data and automatically create collection schema with appropriate attributes", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionName: { type: "string", description: "Name for the new collection" }, + collectionId: { type: "string", description: "Custom collection ID (optional)" }, + sampleData: { + type: "array", + items: { type: "object" }, + description: "Array of sample documents to analyze" + } + }, + required: ["databaseId", "collectionName", "sampleData"] + } +}; + +// suggest_indexes - Analysis recommendation +const suggestIndexesTool = { + name: "suggest_indexes", + description: "Analyze collection usage patterns and recommend optimal indexes", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + queryPatterns: { + type: "array", + items: { type: "string" }, + description: "Common query patterns (optional)" + } + }, + required: ["databaseId", "collectionId"] + } +}; + +// validate_document - Pre-validation +const validateDocumentTool = { + name: "validate_document", + description: "Validate document data against collection schema before creation", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + documentData: { type: "object", description: "Document data to validate" } + }, + required: ["databaseId", "collectionId", "documentData"] + } +}; + +// schema_migration - Advanced schema operation +const schemaMigrationTool = { + name: "schema_migration", + description: "Automatically migrate collection schema with data preservation", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + newSchema: { + type: "array", + items: { type: "object" }, + description: "New schema definition" + }, + migrationStrategy: { + type: "string", + enum: ["safe", "aggressive"], + description: "Migration strategy (safe, aggressive)", + default: "safe" + } + }, + required: ["databaseId", "collectionId", "newSchema"] + } +}; + +// analyze_collection - Data insights analysis +const analyzeCollectionTool = { + name: "analyze_collection", + description: "Get comprehensive data insights and patterns from a collection", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + analysisType: { + type: "string", + enum: ["basic", "detailed", "statistical"], + description: "Type of analysis", + default: "basic" + } + }, + required: ["databaseId", "collectionId"] + } +}; + +// detect_duplicates - Data analysis feature +const detectDuplicatesTool = { + name: "detect_duplicates", + description: "Find potential duplicate records across collections", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + fields: { + type: "array", + items: { type: "string" }, + description: "Fields to check for duplicates" + }, + threshold: { + type: "number", + description: "Similarity threshold (0-1)", + default: 0.9 + } + }, + required: ["databaseId", "collectionId", "fields"] + } +}; + +// data_quality_check - Quality analysis +const dataQualityCheckTool = { + name: "data_quality_check", + description: "Analyze data quality and identify issues (missing fields, invalid formats, etc.)", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + checkType: { + type: "string", + enum: ["completeness", "validity", "consistency", "all"], + description: "Type of quality check", + default: "all" + } + }, + required: ["databaseId", "collectionId"] + } +}; + +// usage_stats - Analytics feature +const usageStatsTool = { + name: "usage_stats", + description: "Get usage statistics and access patterns for collections and documents", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection (optional, for all collections if not specified)" }, + timeRange: { + type: "string", + enum: ["1d", "7d", "30d", "90d"], + description: "Time range for stats", + default: "7d" + } + }, + required: ["databaseId"] + } +}; + +// ===== AVATARS SERVICE (7 tools) ===== + +const avatarTools = [ + { + name: "get_browser_icon", + description: "Get browser icon image URL", + inputSchema: { + type: "object", + properties: { + code: { type: "string", description: "Browser code" }, + width: { type: "number", description: "Icon width (optional)" }, + height: { type: "number", description: "Icon height (optional)" }, + quality: { type: "number", description: "Image quality (optional)" } + }, + required: ["code"] + } + }, + { + name: "get_credit_card_icon", + description: "Get credit card icon image URL", + inputSchema: { + type: "object", + properties: { + code: { type: "string", description: "Credit card code" }, + width: { type: "number", description: "Icon width (optional)" }, + height: { type: "number", description: "Icon height (optional)" }, + quality: { type: "number", description: "Image quality (optional)" } + }, + required: ["code"] + } + }, + { + name: "get_favicon", + description: "Get website favicon URL", + inputSchema: { + type: "object", + properties: { + url: { type: "string", description: "Website URL" } + }, + required: ["url"] + } + }, + { + name: "get_flag_icon", + description: "Get country flag icon URL", + inputSchema: { + type: "object", + properties: { + code: { type: "string", description: "Country code" }, + width: { type: "number", description: "Icon width (optional)" }, + height: { type: "number", description: "Icon height (optional)" }, + quality: { type: "number", description: "Image quality (optional)" } + }, + required: ["code"] + } + }, + { + name: "get_image_from_url", + description: "Get image from URL with transformations", + inputSchema: { + type: "object", + properties: { + url: { type: "string", description: "Image URL" }, + width: { type: "number", description: "Image width (optional)" }, + height: { type: "number", description: "Image height (optional)" } + }, + required: ["url"] + } + }, + { + name: "get_initials_avatar", + description: "Generate initials avatar URL", + inputSchema: { + type: "object", + properties: { + name: { type: "string", description: "Name for initials" }, + width: { type: "number", description: "Avatar width (optional)" }, + height: { type: "number", description: "Avatar height (optional)" }, + background: { type: "string", description: "Background color (optional)" } + }, + required: ["name"] + } + }, + { + name: "get_qr_code", + description: "Generate QR code URL", + inputSchema: { + type: "object", + properties: { + text: { type: "string", description: "Text to encode" }, + size: { type: "number", description: "QR code size (optional)" }, + margin: { type: "number", description: "QR code margin (optional)" }, + download: { type: "boolean", description: "Force download (optional)" } + }, + required: ["text"] + } + } +]; + +// ===== LOCALE SERVICE (5 tools) ===== + +const localeTools = [ + { + name: "list_countries", + description: "List all countries with phone codes" + }, + { + name: "list_continents", + description: "List all continents" + }, + { + name: "list_currencies", + description: "List all currencies" + }, + { + name: "list_languages", + description: "List all languages" + }, + { + name: "list_phone_codes", + description: "List phone codes for all countries" + } +]; + +// ===== ADVANCED USER MANAGEMENT (12 tools) ===== + +const advancedUserTools = [ + "verify_user_email", + "unverify_user_email", + "verify_user_phone", + "unverify_user_phone", + "update_user_mfa", + "create_user_mfa_recovery_codes", + "update_user_phone_verification", + "update_user_password_hash", + "list_user_identities", + "delete_user_identity", + "update_user_labels", + "get_user_preferences" +]; + +// ===== MESSAGING TARGETS (5 tools) ===== + +const messagingTargetTools = [ + "create_user_target", + "list_user_targets", + "get_user_target", + "update_user_target", + "delete_user_target" +]; + +// ===== HEALTH MONITORING (2 tools - kept get_health only) ===== + +const healthTools = [ + "get_health_db", + "get_health_storage" +]; + +// ===== SESSION MANAGEMENT (3 tools) ===== + +const sessionTools = [ + "create_session", + "delete_session", + "list_sessions" +]; + +// ===== FUNCTION EXECUTION (1 tool) ===== + +const functionExecutionTools = [ + "execute_function" +]; + +/** + * TOOL IMPLEMENTATIONS + * + * The actual implementation code for these tools was removed from the main file. + * If you need to restore any of these tools: + * + * 1. Add the tool definition back to the tools list in setupToolHandlers() + * 2. Add the case handler back to the handleTool() switch statement + * 3. Restore the implementation method to the class + * + * The implementations used the standard pattern: + * - Validate configuration + * - Parse input parameters + * - Call appropriate Appwrite service method + * - Return formatted response + * - Handle errors gracefully + */ + +export { + // Data Analysis Tools + autoDetectSchemaTool, + suggestIndexesTool, + validateDocumentTool, + schemaMigrationTool, + analyzeCollectionTool, + detectDuplicatesTool, + dataQualityCheckTool, + usageStatsTool, + + // Avatar Tools + avatarTools, + + // Locale Tools + localeTools, + + // Advanced User Tools + advancedUserTools, + + // Messaging Target Tools + messagingTargetTools, + + // Health Tools + healthTools, + + // Session Tools + sessionTools, + + // Function Execution Tools + functionExecutionTools +}; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 5023e9c..b488c59 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,8 +18,6 @@ import { Account, Health, Messaging, - Locale, - Avatars, ID, Query, Permission, @@ -48,8 +46,6 @@ class AppwriteMCPServer { private account: Account | null = null; private health: Health | null = null; private messaging: Messaging | null = null; - private locale: Locale | null = null; - private avatars: Avatars | null = null; private config: AppwriteConfig | null = null; constructor() { @@ -107,8 +103,6 @@ class AppwriteMCPServer { this.account = new Account(this.client); this.health = new Health(this.client); this.messaging = new Messaging(this.client); - this.locale = new Locale(this.client); - this.avatars = new Avatars(this.client); } private setupToolHandlers(): void { @@ -545,39 +539,17 @@ class AppwriteMCPServer { } }, { - name: "update_user_email", - description: "Update user email", + 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" } + email: { type: "string", description: "New email (optional)" }, + name: { type: "string", description: "New name (optional)" }, + password: { type: "string", description: "New password (optional)" } }, - required: ["userId", "email"] - } - }, - { - name: "update_user_name", - description: "Update user name", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" }, - name: { type: "string", description: "New name" } - }, - required: ["userId", "name"] - } - }, - { - name: "update_user_password", - description: "Update user password", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" }, - password: { type: "string", description: "New password" } - }, - required: ["userId", "password"] + required: ["userId"] } }, { @@ -591,121 +563,6 @@ class AppwriteMCPServer { required: ["userId"] } }, - { - name: "verify_user_email", - description: "Verify a user's email address", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" } - }, - required: ["userId"] - } - }, - { - name: "unverify_user_email", - description: "Unverify a user's email address", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" } - }, - required: ["userId"] - } - }, - { - name: "update_user_mfa", - description: "Enable or disable MFA for a user", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" }, - mfa: { type: "boolean", description: "Enable/disable MFA" } - }, - required: ["userId", "mfa"] - } - }, - { - name: "create_user_mfa_recovery_codes", - description: "Generate MFA recovery codes for a user", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" } - }, - required: ["userId"] - } - }, - { - name: "update_user_phone_verification", - description: "Update user phone verification status", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" }, - phoneVerification: { type: "boolean", description: "Phone verification status" } - }, - required: ["userId", "phoneVerification"] - } - }, - { - name: "verify_user_phone", - description: "Verify a user's phone number", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" } - }, - required: ["userId"] - } - }, - { - name: "unverify_user_phone", - description: "Unverify a user's phone number", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" } - }, - required: ["userId"] - } - }, - { - name: "update_user_password_hash", - description: "Update user password with specific hash algorithm", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" }, - password: { type: "string", description: "New password" }, - hash: { type: "string", description: "Hash algorithm (argon2, bcrypt, md5, sha, scrypt, scryptMod, phpass)" } - }, - required: ["userId", "password", "hash"] - } - }, - { - name: "update_user_labels", - description: "Update user labels (alphanumeric only: A-Z, a-z, 0-9)", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" }, - labels: { type: "array", description: "User labels (max 1000, 1-36 chars each, alphanumeric only)", items: { type: "string" } } - }, - required: ["userId", "labels"] - } - }, - { - name: "get_user_preferences", - description: "Get user preferences", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" } - }, - required: ["userId"] - } - }, { name: "update_user_preferences", description: "Update user preferences", @@ -718,94 +575,6 @@ class AppwriteMCPServer { required: ["userId", "prefs"] } }, - { - name: "list_user_identities", - description: "List user identities", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" } - }, - required: ["userId"] - } - }, - { - name: "delete_user_identity", - description: "Delete user identity", - inputSchema: { - type: "object", - properties: { - identityId: { type: "string", description: "Identity ID" } - }, - required: ["identityId"] - } - }, - { - name: "create_user_target", - description: "Create a messaging target for user", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" }, - targetId: { type: "string", description: "Target ID (optional)" }, - providerType: { type: "string", description: "Provider type (email, sms, push)" }, - identifier: { type: "string", description: "Target identifier" }, - providerId: { type: "string", description: "Provider ID (optional)" }, - name: { type: "string", description: "Target name (optional)" } - }, - required: ["userId", "providerType", "identifier"] - } - }, - { - name: "list_user_targets", - description: "List user messaging targets", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" } - }, - required: ["userId"] - } - }, - { - name: "get_user_target", - description: "Get user messaging target", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" }, - targetId: { type: "string", description: "Target ID" } - }, - required: ["userId", "targetId"] - } - }, - { - name: "update_user_target", - description: "Update user messaging target", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" }, - targetId: { type: "string", description: "Target ID" }, - identifier: { type: "string", description: "Target identifier (optional)" }, - providerId: { type: "string", description: "Provider ID (optional)" }, - name: { type: "string", description: "Target name (optional)" } - }, - required: ["userId", "targetId"] - } - }, - { - name: "delete_user_target", - description: "Delete user messaging target", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" }, - targetId: { type: "string", description: "Target ID" } - }, - required: ["userId", "targetId"] - } - }, // Storage Operations { @@ -889,50 +658,27 @@ class AppwriteMCPServer { } }, { - name: "get_file_download", - description: "Get file download URL", - inputSchema: { - type: "object", - properties: { - bucketId: { type: "string", description: "Bucket ID" }, - fileId: { type: "string", description: "File ID" } - }, - required: ["bucketId", "fileId"] - } - }, - { - name: "get_file_preview", - description: "Get file preview URL with optional image transformations", + 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" }, - width: { type: "number", description: "Preview width (optional)" }, - height: { type: "number", description: "Preview height (optional)" }, - gravity: { type: "string", description: "Image gravity (optional)" }, - quality: { type: "number", description: "Image quality 0-100 (optional)" }, - borderWidth: { type: "number", description: "Border width (optional)" }, - borderColor: { type: "string", description: "Border color (optional)" }, - borderRadius: { type: "number", description: "Border radius (optional)" }, - opacity: { type: "number", description: "Opacity 0-1 (optional)" }, - rotation: { type: "number", description: "Rotation in degrees (optional)" }, - background: { type: "string", description: "Background color (optional)" }, - output: { type: "string", description: "Output format (optional)" } + 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"] - } - }, - { - name: "get_file_view", - description: "Get file view URL", - inputSchema: { - type: "object", - properties: { - bucketId: { type: "string", description: "Bucket ID" }, - fileId: { type: "string", description: "File ID" } - }, - required: ["bucketId", "fileId"] + required: ["bucketId", "fileId", "type"] } }, { @@ -1051,31 +797,6 @@ class AppwriteMCPServer { required: ["functionId"] } }, - { - name: "get_function_deployment", - description: "Get function deployment by ID", - inputSchema: { - type: "object", - properties: { - functionId: { type: "string", description: "Function ID" }, - deploymentId: { type: "string", description: "Deployment ID" } - }, - required: ["functionId", "deploymentId"] - } - }, - { - name: "update_function_deployment", - description: "Update function deployment status", - inputSchema: { - type: "object", - properties: { - functionId: { type: "string", description: "Function ID" }, - deploymentId: { type: "string", description: "Deployment ID" }, - status: { type: "string", description: "Deployment status" } - }, - required: ["functionId", "deploymentId", "status"] - } - }, { name: "delete_function_deployment", description: "Delete function deployment", @@ -1433,291 +1154,11 @@ class AppwriteMCPServer { } }, - // Locale Operations - { - name: "list_countries", - description: "List all countries with phone codes", - inputSchema: { type: "object", properties: {} } - }, - { - name: "list_continents", - description: "List all continents", - inputSchema: { type: "object", properties: {} } - }, - { - name: "list_currencies", - description: "List all currencies", - inputSchema: { type: "object", properties: {} } - }, - { - name: "list_languages", - description: "List all languages", - inputSchema: { type: "object", properties: {} } - }, - { - name: "list_phone_codes", - description: "List phone codes for all countries", - inputSchema: { type: "object", properties: {} } - }, - - // Avatars Operations - { - name: "get_browser_icon", - description: "Get browser icon image URL", - inputSchema: { - type: "object", - properties: { - code: { type: "string", description: "Browser code" }, - width: { type: "number", description: "Icon width (optional)" }, - height: { type: "number", description: "Icon height (optional)" }, - quality: { type: "number", description: "Image quality (optional)" } - }, - required: ["code"] - } - }, - { - name: "get_credit_card_icon", - description: "Get credit card icon image URL", - inputSchema: { - type: "object", - properties: { - code: { type: "string", description: "Credit card code" }, - width: { type: "number", description: "Icon width (optional)" }, - height: { type: "number", description: "Icon height (optional)" }, - quality: { type: "number", description: "Image quality (optional)" } - }, - required: ["code"] - } - }, - { - name: "get_favicon", - description: "Get website favicon URL", - inputSchema: { - type: "object", - properties: { - url: { type: "string", description: "Website URL" } - }, - required: ["url"] - } - }, - { - name: "get_flag_icon", - description: "Get country flag icon URL", - inputSchema: { - type: "object", - properties: { - code: { type: "string", description: "Country code" }, - width: { type: "number", description: "Icon width (optional)" }, - height: { type: "number", description: "Icon height (optional)" }, - quality: { type: "number", description: "Image quality (optional)" } - }, - required: ["code"] - } - }, - { - name: "get_image_from_url", - description: "Get image from URL with transformations", - inputSchema: { - type: "object", - properties: { - url: { type: "string", description: "Image URL" }, - width: { type: "number", description: "Image width (optional)" }, - height: { type: "number", description: "Image height (optional)" } - }, - required: ["url"] - } - }, - { - name: "get_initials_avatar", - description: "Generate initials avatar URL", - inputSchema: { - type: "object", - properties: { - name: { type: "string", description: "Name for initials" }, - width: { type: "number", description: "Avatar width (optional)" }, - height: { type: "number", description: "Avatar height (optional)" }, - background: { type: "string", description: "Background color (optional)" } - }, - required: ["name"] - } - }, - { - name: "get_qr_code", - description: "Generate QR code URL", - inputSchema: { - type: "object", - properties: { - text: { type: "string", description: "Text to encode" }, - size: { type: "number", description: "QR code size (optional)" }, - margin: { type: "number", description: "QR code margin (optional)" }, - download: { type: "boolean", description: "Force download (optional)" } - }, - required: ["text"] - } - }, - - // Smart Schema Operations - { - name: "auto_detect_schema", - description: "Analyze sample data and automatically create collection schema with appropriate attributes", - inputSchema: { - type: "object", - properties: { - databaseId: { type: "string", description: "ID of the database" }, - collectionName: { type: "string", description: "Name for the new collection" }, - sampleData: { type: "array", description: "Array of sample documents to analyze", items: { type: "object" } }, - collectionId: { type: "string", description: "Custom collection ID (optional)" } - }, - required: ["databaseId", "collectionName", "sampleData"] - } - }, - { - name: "suggest_indexes", - description: "Analyze collection usage patterns and recommend optimal indexes", - inputSchema: { - type: "object", - properties: { - databaseId: { type: "string", description: "ID of the database" }, - collectionId: { type: "string", description: "ID of the collection" }, - queryPatterns: { type: "array", description: "Common query patterns (optional)", items: { type: "string" } } - }, - required: ["databaseId", "collectionId"] - } - }, - { - name: "validate_document", - description: "Validate document data against collection schema before creation", - inputSchema: { - type: "object", - properties: { - databaseId: { type: "string", description: "ID of the database" }, - collectionId: { type: "string", description: "ID of the collection" }, - documentData: { type: "object", description: "Document data to validate" } - }, - required: ["databaseId", "collectionId", "documentData"] - } - }, - { - name: "schema_migration", - description: "Automatically migrate collection schema with data preservation", - inputSchema: { - type: "object", - properties: { - databaseId: { type: "string", description: "ID of the database" }, - collectionId: { type: "string", description: "ID of the collection" }, - newSchema: { type: "array", description: "New schema definition", items: { type: "object" } }, - migrationStrategy: { type: "string", description: "Migration strategy (safe, aggressive)", enum: ["safe", "aggressive"], default: "safe" } - }, - required: ["databaseId", "collectionId", "newSchema"] - } - }, - // Data Analysis & Insights - { - name: "analyze_collection", - description: "Get comprehensive data insights and patterns from a collection", - inputSchema: { - type: "object", - properties: { - databaseId: { type: "string", description: "ID of the database" }, - collectionId: { type: "string", description: "ID of the collection" }, - analysisType: { type: "string", description: "Type of analysis", enum: ["basic", "detailed", "statistical"], default: "basic" } - }, - required: ["databaseId", "collectionId"] - } - }, - { - name: "detect_duplicates", - description: "Find potential duplicate records across collections", - inputSchema: { - type: "object", - properties: { - databaseId: { type: "string", description: "ID of the database" }, - collectionId: { type: "string", description: "ID of the collection" }, - fields: { type: "array", description: "Fields to check for duplicates", items: { type: "string" } }, - threshold: { type: "number", description: "Similarity threshold (0-1)", default: 0.9 } - }, - required: ["databaseId", "collectionId", "fields"] - } - }, - { - name: "data_quality_check", - description: "Analyze data quality and identify issues (missing fields, invalid formats, etc.)", - inputSchema: { - type: "object", - properties: { - databaseId: { type: "string", description: "ID of the database" }, - collectionId: { type: "string", description: "ID of the collection" }, - checkType: { type: "string", description: "Type of quality check", enum: ["completeness", "validity", "consistency", "all"], default: "all" } - }, - required: ["databaseId", "collectionId"] - } - }, - { - name: "usage_stats", - description: "Get usage statistics and access patterns for collections and documents", - inputSchema: { - type: "object", - properties: { - databaseId: { type: "string", description: "ID of the database" }, - collectionId: { type: "string", description: "ID of the collection (optional, for all collections if not specified)" }, - timeRange: { type: "string", description: "Time range for stats", enum: ["1d", "7d", "30d", "90d"], default: "7d" } - }, - required: ["databaseId"] - } - }, - // Session Management - { - name: "create_session", - description: "Create a new user session with email and password", - inputSchema: { - type: "object", - properties: { - email: { type: "string", description: "User email" }, - password: { type: "string", description: "User password" } - }, - required: ["email", "password"] - } - }, - { - name: "delete_session", - description: "Delete a user session (requires 'account' scope in API key)", - inputSchema: { - type: "object", - properties: { - sessionId: { type: "string", description: "Session ID to delete" } - }, - required: ["sessionId"] - } - }, - { - name: "list_sessions", - description: "List all sessions for a user", - inputSchema: { - type: "object", - properties: { - userId: { type: "string", description: "User ID" } - }, - required: ["userId"] - } - }, // Function Execution - { - name: "execute_function", - description: "Execute a function with optional data", - inputSchema: { - type: "object", - properties: { - functionId: { type: "string", description: "Function ID" }, - data: { type: "string", description: "Data to pass to function (optional)" }, - async: { type: "boolean", description: "Execute asynchronously (optional)", default: false } - }, - required: ["functionId"] - } - }, { name: "list_executions", description: "List function executions with logs", @@ -1752,22 +1193,6 @@ class AppwriteMCPServer { properties: {} } }, - { - name: "get_health_db", - description: "Get database health status", - inputSchema: { - type: "object", - properties: {} - } - }, - { - name: "get_health_storage", - description: "Get storage health status", - inputSchema: { - type: "object", - properties: {} - } - }, // Bulk Operations { @@ -1980,50 +1405,12 @@ class AppwriteMCPServer { return await this.listUsers(request.params.arguments); case "get_user": return await this.getUser(request.params.arguments); - case "update_user_email": - return await this.updateUserEmail(request.params.arguments); - case "update_user_name": - return await this.updateUserName(request.params.arguments); - case "update_user_password": - return await this.updateUserPassword(request.params.arguments); + case "update_user": + return await this.updateUser(request.params.arguments); case "delete_user": return await this.deleteUser(request.params.arguments); - case "verify_user_email": - return await this.verifyUserEmail(request.params.arguments); - case "unverify_user_email": - return await this.unverifyUserEmail(request.params.arguments); - case "update_user_mfa": - return await this.updateUserMfa(request.params.arguments); - case "create_user_mfa_recovery_codes": - return await this.createUserMfaRecoveryCodes(request.params.arguments); - case "update_user_phone_verification": - return await this.updateUserPhoneVerification(request.params.arguments); - case "verify_user_phone": - return await this.verifyUserPhone(request.params.arguments); - case "unverify_user_phone": - return await this.unverifyUserPhone(request.params.arguments); - case "update_user_password_hash": - return await this.updateUserPasswordHash(request.params.arguments); - case "update_user_labels": - return await this.updateUserLabels(request.params.arguments); - case "get_user_preferences": - return await this.getUserPreferences(request.params.arguments); case "update_user_preferences": return await this.updateUserPreferences(request.params.arguments); - case "list_user_identities": - return await this.listUserIdentities(request.params.arguments); - case "delete_user_identity": - return await this.deleteUserIdentity(request.params.arguments); - case "create_user_target": - return await this.createUserTarget(request.params.arguments); - case "list_user_targets": - return await this.listUserTargets(request.params.arguments); - case "get_user_target": - return await this.getUserTarget(request.params.arguments); - case "update_user_target": - return await this.updateUserTarget(request.params.arguments); - case "delete_user_target": - return await this.deleteUserTarget(request.params.arguments); // Storage Operations case "create_bucket": @@ -2040,12 +1427,8 @@ class AppwriteMCPServer { return await this.listFiles(request.params.arguments); case "get_file": return await this.getFile(request.params.arguments); - case "get_file_download": - return await this.getFileDownload(request.params.arguments); - case "get_file_preview": - return await this.getFilePreview(request.params.arguments); - case "get_file_view": - return await this.getFileView(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": @@ -2066,10 +1449,6 @@ class AppwriteMCPServer { return await this.deleteFunction(request.params.arguments); case "list_function_deployments": return await this.listFunctionDeployments(request.params.arguments); - case "get_function_deployment": - return await this.getFunctionDeployment(request.params.arguments); - case "update_function_deployment": - return await this.updateFunctionDeployment(request.params.arguments); case "delete_function_deployment": return await this.deleteFunctionDeployment(request.params.arguments); case "create_function_variable": @@ -2135,66 +1514,7 @@ class AppwriteMCPServer { case "delete_messaging_subscriber": return await this.deleteMessagingSubscriber(request.params.arguments); - // Locale Operations - case "list_countries": - return await this.listCountries(); - case "list_continents": - return await this.listContinents(); - case "list_currencies": - return await this.listCurrencies(); - case "list_languages": - return await this.listLanguages(); - case "list_phone_codes": - return await this.listPhoneCodes(); - - // Avatars Operations - case "get_browser_icon": - return await this.getBrowserIcon(request.params.arguments); - case "get_credit_card_icon": - return await this.getCreditCardIcon(request.params.arguments); - case "get_favicon": - return await this.getFavicon(request.params.arguments); - case "get_flag_icon": - return await this.getFlagIcon(request.params.arguments); - case "get_image_from_url": - return await this.getImageFromUrl(request.params.arguments); - case "get_initials_avatar": - return await this.getInitialsAvatar(request.params.arguments); - case "get_qr_code": - return await this.getQrCode(request.params.arguments); - - // Smart Schema Operations - case "auto_detect_schema": - return await this.autoDetectSchema(request.params.arguments); - case "suggest_indexes": - return await this.suggestIndexes(request.params.arguments); - case "validate_document": - return await this.validateDocument(request.params.arguments); - case "schema_migration": - return await this.schemaMigration(request.params.arguments); - - - // Data Analysis & Insights - case "analyze_collection": - return await this.analyzeCollection(request.params.arguments); - case "detect_duplicates": - return await this.detectDuplicates(request.params.arguments); - case "data_quality_check": - return await this.dataQualityCheck(request.params.arguments); - case "usage_stats": - return await this.usageStats(request.params.arguments); - - // Session Management - case "create_session": - return await this.createSession(request.params.arguments); - case "delete_session": - return await this.deleteSession(request.params.arguments); - case "list_sessions": - return await this.listSessions(request.params.arguments); - // Function Execution - case "execute_function": - return await this.executeFunction(request.params.arguments); case "list_executions": return await this.listExecutions(request.params.arguments); case "get_execution": @@ -2203,10 +1523,6 @@ class AppwriteMCPServer { // Health Monitoring case "get_health": return await this.getHealth(); - case "get_health_db": - return await this.getHealthDb(); - case "get_health_storage": - return await this.getHealthStorage(); // Bulk Operations case "bulk_create_users": @@ -2989,52 +2305,37 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}` }; } - private async updateUserEmail(args: any) { + private async updateUser(args: any) { if (!this.users) throw new Error("Users service not initialized"); - const { userId, email } = args; + const { userId, email, name, password } = args; - const user = await this.users.updateEmail(userId, email); + 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} email updated to: ${email}` - } - ] - }; - } - - private async updateUserName(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId, name } = args; - - const user = await this.users.updateName(userId, name); - - return { - content: [ - { - type: "text", - text: `User ${userId} name updated to: ${name}` - } - ] - }; - } - - private async updateUserPassword(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId, password } = args; - - const user = await this.users.updatePassword(userId, password); - - return { - content: [ - { - type: "text", - text: `User ${userId} password updated successfully` + text: `User ${userId} - ${updates.join(', ')}` } ] }; @@ -3057,188 +2358,6 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}` }; } - private async verifyUserEmail(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId } = args; - - await this.users.updateEmailVerification(userId, true); - - return { - content: [ - { - type: "text", - text: `User ${userId} email verification status updated to verified ✅` - } - ] - }; - } - - private async unverifyUserEmail(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId } = args; - - await this.users.updateEmailVerification(userId, false); - - return { - content: [ - { - type: "text", - text: `User ${userId} email verification status updated to unverified ❌` - } - ] - }; - } - - private async updateUserMfa(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId, mfa } = args; - - await this.users.updateMfa(userId, mfa); - - return { - content: [ - { - type: "text", - text: `User ${userId} MFA ${mfa ? 'enabled ✅' : 'disabled ❌'}` - } - ] - }; - } - - private async createUserMfaRecoveryCodes(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId } = args; - - const result = await this.users.createMfaRecoveryCodes(userId) as any; - - return { - content: [ - { - type: "text", - text: `MFA recovery codes generated for user ${userId}:\n${result.recoveryCodes?.join('\n') || 'No codes returned'}` - } - ] - }; - } - - private async updateUserPhoneVerification(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId, phoneVerification } = args; - - await this.users.updatePhoneVerification(userId, phoneVerification); - - return { - content: [ - { - type: "text", - text: `User ${userId} phone verification ${phoneVerification ? 'verified ✅' : 'unverified ❌'}` - } - ] - }; - } - - private async verifyUserPhone(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId } = args; - - await this.users.updatePhoneVerification(userId, true); - - return { - content: [ - { - type: "text", - text: `User ${userId} phone verification status updated to verified ✅` - } - ] - }; - } - - private async unverifyUserPhone(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId } = args; - - await this.users.updatePhoneVerification(userId, false); - - return { - content: [ - { - type: "text", - text: `User ${userId} phone verification status updated to unverified ❌` - } - ] - }; - } - - private async updateUserPasswordHash(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId, password, hash } = args; - - await this.users.updatePassword(userId, password); - - return { - content: [ - { - type: "text", - text: `User ${userId} password updated with ${hash} hash algorithm` - } - ] - }; - } - - private async updateUserLabels(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId, labels } = args; - - // Validate labels: alphanumeric only, 1-36 chars each - const validLabels = labels.filter((label: string) => { - const isValid = /^[A-Za-z0-9]{1,36}$/.test(label); - if (!isValid) { - console.warn(`Invalid label: "${label}" - must be alphanumeric, 1-36 characters`); - } - return isValid; - }); - - if (validLabels.length !== labels.length) { - throw new Error(`Invalid labels detected. Labels must be alphanumeric (A-Z, a-z, 0-9) and 1-36 characters long. Invalid: ${labels.filter((l: string) => !/^[A-Za-z0-9]{1,36}$/.test(l)).join(', ')}`); - } - - const user = await this.users.updateLabels(userId, validLabels) as any; - - return { - content: [ - { - type: "text", - text: `User ${userId} labels updated:\n${validLabels.join(', ')}` - } - ] - }; - } - - private async getUserPreferences(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId } = args; - - const prefs = await this.users.getPrefs(userId) as any; - - return { - content: [ - { - type: "text", - text: `User ${userId} preferences:\n${JSON.stringify(prefs, null, 2)}` - } - ] - }; - } private async updateUserPreferences(args: any) { if (!this.users) throw new Error("Users service not initialized"); @@ -3257,155 +2376,6 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}` }; } - private async listUserIdentities(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId } = args; - - try { - // Try without queries first - let identities; - try { - identities = await this.users.listIdentities(userId) as any; - } catch (error: any) { - if (error.message.includes('queries')) { - // If queries error, try with empty array - identities = await (this.users as any).listIdentities(userId, []); - } else { - throw error; - } - } - - const identityList = identities.identities.map((identity: any) => - `- Provider: ${identity.provider} - ID: ${identity.$id}` - ).join('\n'); - - return { - content: [ - { - type: "text", - text: `User ${userId} identities (${identities.total}):\n${identityList}` - } - ] - }; - } catch (error: any) { - return { - content: [ - { - type: "text", - text: `Error listing identities: ${error.message}` - } - ] - }; - } - } - - private async deleteUserIdentity(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { identityId } = args; - - await this.users.deleteIdentity(identityId); - - return { - content: [ - { - type: "text", - text: `Identity ${identityId} deleted successfully` - } - ] - }; - } - - private async createUserTarget(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId, targetId, providerType, identifier, providerId, name } = args; - const tid = targetId || ID.unique(); - - const target = await this.users.createTarget(userId, tid, providerType, identifier, providerId, name) as any; - - return { - content: [ - { - type: "text", - text: `Target created for user ${userId}:\n- ID: ${target.$id}\n- Type: ${target.providerType}\n- Identifier: ${target.identifier}` - } - ] - }; - } - - private async listUserTargets(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId } = args; - - const targets = await this.users.listTargets(userId) as any; - - const targetList = targets.targets.map((target: any) => - `- ${target.name || target.identifier} (${target.providerType}) - ID: ${target.$id}` - ).join('\n'); - - return { - content: [ - { - type: "text", - text: `User ${userId} targets (${targets.total}):\n${targetList}` - } - ] - }; - } - - private async getUserTarget(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId, targetId } = args; - - const target = await this.users.getTarget(userId, targetId) as any; - - return { - content: [ - { - type: "text", - text: `User ${userId} target ${targetId}:\n${JSON.stringify(target, null, 2)}` - } - ] - }; - } - - private async updateUserTarget(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId, targetId, identifier, providerId, name } = args; - - await this.users.updateTarget(userId, targetId, identifier, providerId, name); - - return { - content: [ - { - type: "text", - text: `User ${userId} target ${targetId} updated successfully` - } - ] - }; - } - - private async deleteUserTarget(args: any) { - if (!this.users) throw new Error("Users service not initialized"); - - const { userId, targetId } = args; - - await this.users.deleteTarget(userId, targetId); - - return { - content: [ - { - type: "text", - text: `User ${userId} target ${targetId} deleted successfully` - } - ] - }; - } // Storage Operations private async createBucket(args: any) { @@ -3534,69 +2504,55 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}` }; } - private async getFileDownload(args: any) { + private async getFileUrl(args: any) { if (!this.storage) throw new Error("Storage service not initialized"); - const { bucketId, fileId } = args; + const { bucketId, fileId, type, width, height, gravity, quality, borderWidth, borderColor, borderRadius, opacity, rotation, background, output } = args; - // For server-side usage, we construct the download URL - const downloadUrl = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/download?project=${this.config?.projectId}`; + 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: `Download URL for file ${fileId}:\n${downloadUrl}` - } - ] - }; - } - - private async getFilePreview(args: any) { - if (!this.storage) throw new Error("Storage service not initialized"); - - const { bucketId, fileId, width, height, gravity, quality, borderWidth, borderColor, borderRadius, opacity, rotation, background, output } = args; - - // Build query parameters - 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 || ''); - - const previewUrl = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/preview?${params.toString()}`; - - return { - content: [ - { - type: "text", - text: `Preview URL for file ${fileId}:\n${previewUrl}` - } - ] - }; - } - - private async getFileView(args: any) { - if (!this.storage) throw new Error("Storage service not initialized"); - - const { bucketId, fileId } = args; - - const viewUrl = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/view?project=${this.config?.projectId}`; - - return { - content: [ - { - type: "text", - text: `View URL for file ${fileId}:\n${viewUrl}` + text: `${urlType} URL for file ${fileId}:\n${url}` } ] }; @@ -3783,39 +2739,6 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}` }; } - private async getFunctionDeployment(args: any) { - if (!this.functions) throw new Error("Functions service not initialized"); - - const { functionId, deploymentId } = args; - - const deployment = await this.functions.getDeployment(functionId, deploymentId) as any; - - return { - content: [ - { - type: "text", - text: `Function ${functionId} deployment ${deploymentId}:\n${JSON.stringify(deployment, null, 2)}` - } - ] - }; - } - - private async updateFunctionDeployment(args: any) { - if (!this.functions) throw new Error("Functions service not initialized"); - - const { functionId, deploymentId, status } = args; - - const deployment = await this.functions.updateDeployment(functionId, deploymentId) as any; - - return { - content: [ - { - type: "text", - text: `Function ${functionId} deployment ${deploymentId} status updated to: ${status}` - } - ] - }; - } private async deleteFunctionDeployment(args: any) { if (!this.functions) throw new Error("Functions service not initialized"); @@ -4507,265 +3430,7 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}` }; } - // Locale Operations - private async listCountries() { - if (!this.locale) throw new Error("Locale service not initialized"); - - const countries = await this.locale.listCountries() as any; - - const countryList = countries.countries.map((country: any) => { - // Try to find the actual numeric phone code property - const phoneCode = country.phoneCode || country.dialCode || country.countryCode || country.callingCode || country.dialingCode; - return `- ${country.name} (${country.code}) - Phone: +${phoneCode || 'N/A'}`; - }).join('\n'); - - return { - content: [ - { - type: "text", - text: `Countries (${countries.total}):\n${countryList}` - } - ] - }; - } - private async listContinents() { - if (!this.locale) throw new Error("Locale service not initialized"); - - const continents = await this.locale.listContinents() as any; - - const continentList = continents.continents.map((continent: any) => - `- ${continent.name} (${continent.code})` - ).join('\n'); - - return { - content: [ - { - type: "text", - text: `Continents (${continents.total}):\n${continentList}` - } - ] - }; - } - - private async listCurrencies() { - if (!this.locale) throw new Error("Locale service not initialized"); - - const currencies = await this.locale.listCurrencies() as any; - - const currencyList = currencies.currencies.map((currency: any) => - `- ${currency.name} (${currency.code}) - Symbol: ${currency.symbol}` - ).join('\n'); - - return { - content: [ - { - type: "text", - text: `Currencies (${currencies.total}):\n${currencyList}` - } - ] - }; - } - - private async listLanguages() { - if (!this.locale) throw new Error("Locale service not initialized"); - - const languages = await this.locale.listLanguages() as any; - - const languageList = languages.languages.map((language: any) => - `- ${language.name} (${language.code})` - ).join('\n'); - - return { - content: [ - { - type: "text", - text: `Languages (${languages.total}):\n${languageList}` - } - ] - }; - } - - private async listPhoneCodes() { - if (!this.locale) throw new Error("Locale service not initialized"); - - try { - // Try using the dedicated listPhones API if available - const phones = await (this.locale as any).listPhones() as any; - - if (phones && phones.phones) { - const phoneCodeList = phones.phones.map((phone: any) => - `- ${phone.countryName}: +${phone.countryCode}` - ).join('\n'); - - return { - content: [ - { - type: "text", - text: `Phone codes (${phones.phones.length}):\n${phoneCodeList}` - } - ] - }; - } - } catch (error) { - // Fall back to countries endpoint - } - - // Fallback: Use listCountries - const countries = await this.locale.listCountries() as any; - - // Debug: Check actual structure - if (countries.countries && countries.countries.length > 0) { - const sampleCountry = countries.countries[0]; - const availableProps = Object.keys(sampleCountry).join(', '); - - const phoneCodeList = countries.countries - .map((country: any) => { - const phoneCode = country.phoneCode || country.dialCode || country.countryCode || country.callingCode; - return phoneCode ? `- ${country.name}: +${phoneCode}` : null; - }) - .filter(Boolean) - .join('\n'); - - return { - content: [ - { - type: "text", - text: `Phone codes (${countries.countries.length}):\n${phoneCodeList || 'No phone codes found'}\n\nDebug - Sample country properties: ${availableProps}` - } - ] - }; - } - - return { - content: [ - { - type: "text", - text: `No countries data found. Response structure: ${JSON.stringify(countries, null, 2)}` - } - ] - }; - } - - // Avatars Operations - private async getBrowserIcon(args: any) { - if (!this.avatars) throw new Error("Avatars service not initialized"); - - const { code, width, height, quality } = args; - - const iconUrl = `${this.config?.apiEndpoint}/avatars/browsers/${code}?project=${this.config?.projectId}${width ? `&width=${width}` : ''}${height ? `&height=${height}` : ''}${quality ? `&quality=${quality}` : ''}`; - - return { - content: [ - { - type: "text", - text: `Browser icon URL for ${code}:\n${iconUrl}` - } - ] - }; - } - - private async getCreditCardIcon(args: any) { - if (!this.avatars) throw new Error("Avatars service not initialized"); - - const { code, width, height, quality } = args; - - const iconUrl = `${this.config?.apiEndpoint}/avatars/credit-cards/${code}?project=${this.config?.projectId}${width ? `&width=${width}` : ''}${height ? `&height=${height}` : ''}${quality ? `&quality=${quality}` : ''}`; - - return { - content: [ - { - type: "text", - text: `Credit card icon URL for ${code}:\n${iconUrl}` - } - ] - }; - } - - private async getFavicon(args: any) { - if (!this.avatars) throw new Error("Avatars service not initialized"); - - const { url } = args; - - const faviconUrl = `${this.config?.apiEndpoint}/avatars/favicon?project=${this.config?.projectId}&url=${encodeURIComponent(url)}`; - - return { - content: [ - { - type: "text", - text: `Favicon URL for ${url}:\n${faviconUrl}` - } - ] - }; - } - - private async getFlagIcon(args: any) { - if (!this.avatars) throw new Error("Avatars service not initialized"); - - const { code, width, height, quality } = args; - - const flagUrl = `${this.config?.apiEndpoint}/avatars/flags/${code}?project=${this.config?.projectId}${width ? `&width=${width}` : ''}${height ? `&height=${height}` : ''}${quality ? `&quality=${quality}` : ''}`; - - return { - content: [ - { - type: "text", - text: `Flag icon URL for ${code}:\n${flagUrl}` - } - ] - }; - } - - private async getImageFromUrl(args: any) { - if (!this.avatars) throw new Error("Avatars service not initialized"); - - const { url, width, height } = args; - - const imageUrl = `${this.config?.apiEndpoint}/avatars/image?project=${this.config?.projectId}&url=${encodeURIComponent(url)}${width ? `&width=${width}` : ''}${height ? `&height=${height}` : ''}`; - - return { - content: [ - { - type: "text", - text: `Transformed image URL:\n${imageUrl}` - } - ] - }; - } - - private async getInitialsAvatar(args: any) { - if (!this.avatars) throw new Error("Avatars service not initialized"); - - const { name, width, height, background } = args; - - const avatarUrl = `${this.config?.apiEndpoint}/avatars/initials?project=${this.config?.projectId}&name=${encodeURIComponent(name)}${width ? `&width=${width}` : ''}${height ? `&height=${height}` : ''}${background ? `&background=${background}` : ''}`; - - return { - content: [ - { - type: "text", - text: `Initials avatar URL for "${name}":\n${avatarUrl}` - } - ] - }; - } - - private async getQrCode(args: any) { - if (!this.avatars) throw new Error("Avatars service not initialized"); - - const { text, size, margin, download } = args; - - const qrUrl = `${this.config?.apiEndpoint}/avatars/qr?project=${this.config?.projectId}&text=${encodeURIComponent(text)}${size ? `&size=${size}` : ''}${margin ? `&margin=${margin}` : ''}${download ? `&download=${download}` : ''}`; - - return { - content: [ - { - type: "text", - text: `QR code URL for "${text}":\n${qrUrl}` - } - ] - }; - } // Smart Schema Operations private async autoDetectSchema(args: any) { diff --git a/src/index.ts.backup b/src/index.ts.backup new file mode 100644 index 0000000..5023e9c --- /dev/null +++ b/src/index.ts.backup @@ -0,0 +1,6585 @@ +#!/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, + Locale, + Avatars, + 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 locale: Locale | null = null; + private avatars: Avatars | 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); + this.locale = new Locale(this.client); + this.avatars = new Avatars(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_email", + description: "Update user email", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + email: { type: "string", description: "New email" } + }, + required: ["userId", "email"] + } + }, + { + name: "update_user_name", + description: "Update user name", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + name: { type: "string", description: "New name" } + }, + required: ["userId", "name"] + } + }, + { + name: "update_user_password", + description: "Update user password", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + password: { type: "string", description: "New password" } + }, + required: ["userId", "password"] + } + }, + { + name: "delete_user", + description: "Delete a user", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" } + }, + required: ["userId"] + } + }, + { + name: "verify_user_email", + description: "Verify a user's email address", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" } + }, + required: ["userId"] + } + }, + { + name: "unverify_user_email", + description: "Unverify a user's email address", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" } + }, + required: ["userId"] + } + }, + { + name: "update_user_mfa", + description: "Enable or disable MFA for a user", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + mfa: { type: "boolean", description: "Enable/disable MFA" } + }, + required: ["userId", "mfa"] + } + }, + { + name: "create_user_mfa_recovery_codes", + description: "Generate MFA recovery codes for a user", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" } + }, + required: ["userId"] + } + }, + { + name: "update_user_phone_verification", + description: "Update user phone verification status", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + phoneVerification: { type: "boolean", description: "Phone verification status" } + }, + required: ["userId", "phoneVerification"] + } + }, + { + name: "verify_user_phone", + description: "Verify a user's phone number", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" } + }, + required: ["userId"] + } + }, + { + name: "unverify_user_phone", + description: "Unverify a user's phone number", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" } + }, + required: ["userId"] + } + }, + { + name: "update_user_password_hash", + description: "Update user password with specific hash algorithm", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + password: { type: "string", description: "New password" }, + hash: { type: "string", description: "Hash algorithm (argon2, bcrypt, md5, sha, scrypt, scryptMod, phpass)" } + }, + required: ["userId", "password", "hash"] + } + }, + { + name: "update_user_labels", + description: "Update user labels (alphanumeric only: A-Z, a-z, 0-9)", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + labels: { type: "array", description: "User labels (max 1000, 1-36 chars each, alphanumeric only)", items: { type: "string" } } + }, + required: ["userId", "labels"] + } + }, + { + name: "get_user_preferences", + description: "Get user preferences", + 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"] + } + }, + { + name: "list_user_identities", + description: "List user identities", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" } + }, + required: ["userId"] + } + }, + { + name: "delete_user_identity", + description: "Delete user identity", + inputSchema: { + type: "object", + properties: { + identityId: { type: "string", description: "Identity ID" } + }, + required: ["identityId"] + } + }, + { + name: "create_user_target", + description: "Create a messaging target for user", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + targetId: { type: "string", description: "Target ID (optional)" }, + providerType: { type: "string", description: "Provider type (email, sms, push)" }, + identifier: { type: "string", description: "Target identifier" }, + providerId: { type: "string", description: "Provider ID (optional)" }, + name: { type: "string", description: "Target name (optional)" } + }, + required: ["userId", "providerType", "identifier"] + } + }, + { + name: "list_user_targets", + description: "List user messaging targets", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" } + }, + required: ["userId"] + } + }, + { + name: "get_user_target", + description: "Get user messaging target", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + targetId: { type: "string", description: "Target ID" } + }, + required: ["userId", "targetId"] + } + }, + { + name: "update_user_target", + description: "Update user messaging target", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + targetId: { type: "string", description: "Target ID" }, + identifier: { type: "string", description: "Target identifier (optional)" }, + providerId: { type: "string", description: "Provider ID (optional)" }, + name: { type: "string", description: "Target name (optional)" } + }, + required: ["userId", "targetId"] + } + }, + { + name: "delete_user_target", + description: "Delete user messaging target", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" }, + targetId: { type: "string", description: "Target ID" } + }, + required: ["userId", "targetId"] + } + }, + + // 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_download", + description: "Get file download URL", + inputSchema: { + type: "object", + properties: { + bucketId: { type: "string", description: "Bucket ID" }, + fileId: { type: "string", description: "File ID" } + }, + required: ["bucketId", "fileId"] + } + }, + { + name: "get_file_preview", + description: "Get file preview URL with optional image transformations", + inputSchema: { + type: "object", + properties: { + bucketId: { type: "string", description: "Bucket ID" }, + fileId: { type: "string", description: "File ID" }, + width: { type: "number", description: "Preview width (optional)" }, + height: { type: "number", description: "Preview height (optional)" }, + gravity: { type: "string", description: "Image gravity (optional)" }, + quality: { type: "number", description: "Image quality 0-100 (optional)" }, + borderWidth: { type: "number", description: "Border width (optional)" }, + borderColor: { type: "string", description: "Border color (optional)" }, + borderRadius: { type: "number", description: "Border radius (optional)" }, + opacity: { type: "number", description: "Opacity 0-1 (optional)" }, + rotation: { type: "number", description: "Rotation in degrees (optional)" }, + background: { type: "string", description: "Background color (optional)" }, + output: { type: "string", description: "Output format (optional)" } + }, + required: ["bucketId", "fileId"] + } + }, + { + name: "get_file_view", + description: "Get file view URL", + inputSchema: { + type: "object", + properties: { + bucketId: { type: "string", description: "Bucket ID" }, + fileId: { type: "string", description: "File ID" } + }, + required: ["bucketId", "fileId"] + } + }, + { + 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: "get_function_deployment", + description: "Get function deployment by ID", + inputSchema: { + type: "object", + properties: { + functionId: { type: "string", description: "Function ID" }, + deploymentId: { type: "string", description: "Deployment ID" } + }, + required: ["functionId", "deploymentId"] + } + }, + { + name: "update_function_deployment", + description: "Update function deployment status", + inputSchema: { + type: "object", + properties: { + functionId: { type: "string", description: "Function ID" }, + deploymentId: { type: "string", description: "Deployment ID" }, + status: { type: "string", description: "Deployment status" } + }, + required: ["functionId", "deploymentId", "status"] + } + }, + { + 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"] + } + }, + + // Locale Operations + { + name: "list_countries", + description: "List all countries with phone codes", + inputSchema: { type: "object", properties: {} } + }, + { + name: "list_continents", + description: "List all continents", + inputSchema: { type: "object", properties: {} } + }, + { + name: "list_currencies", + description: "List all currencies", + inputSchema: { type: "object", properties: {} } + }, + { + name: "list_languages", + description: "List all languages", + inputSchema: { type: "object", properties: {} } + }, + { + name: "list_phone_codes", + description: "List phone codes for all countries", + inputSchema: { type: "object", properties: {} } + }, + + // Avatars Operations + { + name: "get_browser_icon", + description: "Get browser icon image URL", + inputSchema: { + type: "object", + properties: { + code: { type: "string", description: "Browser code" }, + width: { type: "number", description: "Icon width (optional)" }, + height: { type: "number", description: "Icon height (optional)" }, + quality: { type: "number", description: "Image quality (optional)" } + }, + required: ["code"] + } + }, + { + name: "get_credit_card_icon", + description: "Get credit card icon image URL", + inputSchema: { + type: "object", + properties: { + code: { type: "string", description: "Credit card code" }, + width: { type: "number", description: "Icon width (optional)" }, + height: { type: "number", description: "Icon height (optional)" }, + quality: { type: "number", description: "Image quality (optional)" } + }, + required: ["code"] + } + }, + { + name: "get_favicon", + description: "Get website favicon URL", + inputSchema: { + type: "object", + properties: { + url: { type: "string", description: "Website URL" } + }, + required: ["url"] + } + }, + { + name: "get_flag_icon", + description: "Get country flag icon URL", + inputSchema: { + type: "object", + properties: { + code: { type: "string", description: "Country code" }, + width: { type: "number", description: "Icon width (optional)" }, + height: { type: "number", description: "Icon height (optional)" }, + quality: { type: "number", description: "Image quality (optional)" } + }, + required: ["code"] + } + }, + { + name: "get_image_from_url", + description: "Get image from URL with transformations", + inputSchema: { + type: "object", + properties: { + url: { type: "string", description: "Image URL" }, + width: { type: "number", description: "Image width (optional)" }, + height: { type: "number", description: "Image height (optional)" } + }, + required: ["url"] + } + }, + { + name: "get_initials_avatar", + description: "Generate initials avatar URL", + inputSchema: { + type: "object", + properties: { + name: { type: "string", description: "Name for initials" }, + width: { type: "number", description: "Avatar width (optional)" }, + height: { type: "number", description: "Avatar height (optional)" }, + background: { type: "string", description: "Background color (optional)" } + }, + required: ["name"] + } + }, + { + name: "get_qr_code", + description: "Generate QR code URL", + inputSchema: { + type: "object", + properties: { + text: { type: "string", description: "Text to encode" }, + size: { type: "number", description: "QR code size (optional)" }, + margin: { type: "number", description: "QR code margin (optional)" }, + download: { type: "boolean", description: "Force download (optional)" } + }, + required: ["text"] + } + }, + + // Smart Schema Operations + { + name: "auto_detect_schema", + description: "Analyze sample data and automatically create collection schema with appropriate attributes", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionName: { type: "string", description: "Name for the new collection" }, + sampleData: { type: "array", description: "Array of sample documents to analyze", items: { type: "object" } }, + collectionId: { type: "string", description: "Custom collection ID (optional)" } + }, + required: ["databaseId", "collectionName", "sampleData"] + } + }, + { + name: "suggest_indexes", + description: "Analyze collection usage patterns and recommend optimal indexes", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + queryPatterns: { type: "array", description: "Common query patterns (optional)", items: { type: "string" } } + }, + required: ["databaseId", "collectionId"] + } + }, + { + name: "validate_document", + description: "Validate document data against collection schema before creation", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + documentData: { type: "object", description: "Document data to validate" } + }, + required: ["databaseId", "collectionId", "documentData"] + } + }, + { + name: "schema_migration", + description: "Automatically migrate collection schema with data preservation", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + newSchema: { type: "array", description: "New schema definition", items: { type: "object" } }, + migrationStrategy: { type: "string", description: "Migration strategy (safe, aggressive)", enum: ["safe", "aggressive"], default: "safe" } + }, + required: ["databaseId", "collectionId", "newSchema"] + } + }, + + + // Data Analysis & Insights + { + name: "analyze_collection", + description: "Get comprehensive data insights and patterns from a collection", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + analysisType: { type: "string", description: "Type of analysis", enum: ["basic", "detailed", "statistical"], default: "basic" } + }, + required: ["databaseId", "collectionId"] + } + }, + { + name: "detect_duplicates", + description: "Find potential duplicate records across collections", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + fields: { type: "array", description: "Fields to check for duplicates", items: { type: "string" } }, + threshold: { type: "number", description: "Similarity threshold (0-1)", default: 0.9 } + }, + required: ["databaseId", "collectionId", "fields"] + } + }, + { + name: "data_quality_check", + description: "Analyze data quality and identify issues (missing fields, invalid formats, etc.)", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection" }, + checkType: { type: "string", description: "Type of quality check", enum: ["completeness", "validity", "consistency", "all"], default: "all" } + }, + required: ["databaseId", "collectionId"] + } + }, + { + name: "usage_stats", + description: "Get usage statistics and access patterns for collections and documents", + inputSchema: { + type: "object", + properties: { + databaseId: { type: "string", description: "ID of the database" }, + collectionId: { type: "string", description: "ID of the collection (optional, for all collections if not specified)" }, + timeRange: { type: "string", description: "Time range for stats", enum: ["1d", "7d", "30d", "90d"], default: "7d" } + }, + required: ["databaseId"] + } + }, + + // Session Management + { + name: "create_session", + description: "Create a new user session with email and password", + inputSchema: { + type: "object", + properties: { + email: { type: "string", description: "User email" }, + password: { type: "string", description: "User password" } + }, + required: ["email", "password"] + } + }, + { + name: "delete_session", + description: "Delete a user session (requires 'account' scope in API key)", + inputSchema: { + type: "object", + properties: { + sessionId: { type: "string", description: "Session ID to delete" } + }, + required: ["sessionId"] + } + }, + { + name: "list_sessions", + description: "List all sessions for a user", + inputSchema: { + type: "object", + properties: { + userId: { type: "string", description: "User ID" } + }, + required: ["userId"] + } + }, + + // Function Execution + { + name: "execute_function", + description: "Execute a function with optional data", + inputSchema: { + type: "object", + properties: { + functionId: { type: "string", description: "Function ID" }, + data: { type: "string", description: "Data to pass to function (optional)" }, + async: { type: "boolean", description: "Execute asynchronously (optional)", default: false } + }, + required: ["functionId"] + } + }, + { + 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: {} + } + }, + { + name: "get_health_db", + description: "Get database health status", + inputSchema: { + type: "object", + properties: {} + } + }, + { + name: "get_health_storage", + description: "Get storage health status", + 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_email": + return await this.updateUserEmail(request.params.arguments); + case "update_user_name": + return await this.updateUserName(request.params.arguments); + case "update_user_password": + return await this.updateUserPassword(request.params.arguments); + case "delete_user": + return await this.deleteUser(request.params.arguments); + case "verify_user_email": + return await this.verifyUserEmail(request.params.arguments); + case "unverify_user_email": + return await this.unverifyUserEmail(request.params.arguments); + case "update_user_mfa": + return await this.updateUserMfa(request.params.arguments); + case "create_user_mfa_recovery_codes": + return await this.createUserMfaRecoveryCodes(request.params.arguments); + case "update_user_phone_verification": + return await this.updateUserPhoneVerification(request.params.arguments); + case "verify_user_phone": + return await this.verifyUserPhone(request.params.arguments); + case "unverify_user_phone": + return await this.unverifyUserPhone(request.params.arguments); + case "update_user_password_hash": + return await this.updateUserPasswordHash(request.params.arguments); + case "update_user_labels": + return await this.updateUserLabels(request.params.arguments); + case "get_user_preferences": + return await this.getUserPreferences(request.params.arguments); + case "update_user_preferences": + return await this.updateUserPreferences(request.params.arguments); + case "list_user_identities": + return await this.listUserIdentities(request.params.arguments); + case "delete_user_identity": + return await this.deleteUserIdentity(request.params.arguments); + case "create_user_target": + return await this.createUserTarget(request.params.arguments); + case "list_user_targets": + return await this.listUserTargets(request.params.arguments); + case "get_user_target": + return await this.getUserTarget(request.params.arguments); + case "update_user_target": + return await this.updateUserTarget(request.params.arguments); + case "delete_user_target": + return await this.deleteUserTarget(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_download": + return await this.getFileDownload(request.params.arguments); + case "get_file_preview": + return await this.getFilePreview(request.params.arguments); + case "get_file_view": + return await this.getFileView(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 "get_function_deployment": + return await this.getFunctionDeployment(request.params.arguments); + case "update_function_deployment": + return await this.updateFunctionDeployment(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); + + // Locale Operations + case "list_countries": + return await this.listCountries(); + case "list_continents": + return await this.listContinents(); + case "list_currencies": + return await this.listCurrencies(); + case "list_languages": + return await this.listLanguages(); + case "list_phone_codes": + return await this.listPhoneCodes(); + + // Avatars Operations + case "get_browser_icon": + return await this.getBrowserIcon(request.params.arguments); + case "get_credit_card_icon": + return await this.getCreditCardIcon(request.params.arguments); + case "get_favicon": + return await this.getFavicon(request.params.arguments); + case "get_flag_icon": + return await this.getFlagIcon(request.params.arguments); + case "get_image_from_url": + return await this.getImageFromUrl(request.params.arguments); + case "get_initials_avatar": + return await this.getInitialsAvatar(request.params.arguments); + case "get_qr_code": + return await this.getQrCode(request.params.arguments); + + // Smart Schema Operations + case "auto_detect_schema": + return await this.autoDetectSchema(request.params.arguments); + case "suggest_indexes": + return await this.suggestIndexes(request.params.arguments); + case "validate_document": + return await this.validateDocument(request.params.arguments); + case "schema_migration": + return await this.schemaMigration(request.params.arguments); + + + // Data Analysis & Insights + case "analyze_collection": + return await this.analyzeCollection(request.params.arguments); + case "detect_duplicates": + return await this.detectDuplicates(request.params.arguments); + case "data_quality_check": + return await this.dataQualityCheck(request.params.arguments); + case "usage_stats": + return await this.usageStats(request.params.arguments); + + // Session Management + case "create_session": + return await this.createSession(request.params.arguments); + case "delete_session": + return await this.deleteSession(request.params.arguments); + case "list_sessions": + return await this.listSessions(request.params.arguments); + + // Function Execution + case "execute_function": + return await this.executeFunction(request.params.arguments); + 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(); + case "get_health_db": + return await this.getHealthDb(); + case "get_health_storage": + return await this.getHealthStorage(); + + // 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 updateUserEmail(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, email } = args; + + const user = await this.users.updateEmail(userId, email); + + return { + content: [ + { + type: "text", + text: `User ${userId} email updated to: ${email}` + } + ] + }; + } + + private async updateUserName(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, name } = args; + + const user = await this.users.updateName(userId, name); + + return { + content: [ + { + type: "text", + text: `User ${userId} name updated to: ${name}` + } + ] + }; + } + + private async updateUserPassword(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, password } = args; + + const user = await this.users.updatePassword(userId, password); + + return { + content: [ + { + type: "text", + text: `User ${userId} password updated successfully` + } + ] + }; + } + + 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 verifyUserEmail(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId } = args; + + await this.users.updateEmailVerification(userId, true); + + return { + content: [ + { + type: "text", + text: `User ${userId} email verification status updated to verified ✅` + } + ] + }; + } + + private async unverifyUserEmail(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId } = args; + + await this.users.updateEmailVerification(userId, false); + + return { + content: [ + { + type: "text", + text: `User ${userId} email verification status updated to unverified ❌` + } + ] + }; + } + + private async updateUserMfa(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, mfa } = args; + + await this.users.updateMfa(userId, mfa); + + return { + content: [ + { + type: "text", + text: `User ${userId} MFA ${mfa ? 'enabled ✅' : 'disabled ❌'}` + } + ] + }; + } + + private async createUserMfaRecoveryCodes(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId } = args; + + const result = await this.users.createMfaRecoveryCodes(userId) as any; + + return { + content: [ + { + type: "text", + text: `MFA recovery codes generated for user ${userId}:\n${result.recoveryCodes?.join('\n') || 'No codes returned'}` + } + ] + }; + } + + private async updateUserPhoneVerification(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, phoneVerification } = args; + + await this.users.updatePhoneVerification(userId, phoneVerification); + + return { + content: [ + { + type: "text", + text: `User ${userId} phone verification ${phoneVerification ? 'verified ✅' : 'unverified ❌'}` + } + ] + }; + } + + private async verifyUserPhone(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId } = args; + + await this.users.updatePhoneVerification(userId, true); + + return { + content: [ + { + type: "text", + text: `User ${userId} phone verification status updated to verified ✅` + } + ] + }; + } + + private async unverifyUserPhone(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId } = args; + + await this.users.updatePhoneVerification(userId, false); + + return { + content: [ + { + type: "text", + text: `User ${userId} phone verification status updated to unverified ❌` + } + ] + }; + } + + private async updateUserPasswordHash(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, password, hash } = args; + + await this.users.updatePassword(userId, password); + + return { + content: [ + { + type: "text", + text: `User ${userId} password updated with ${hash} hash algorithm` + } + ] + }; + } + + private async updateUserLabels(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, labels } = args; + + // Validate labels: alphanumeric only, 1-36 chars each + const validLabels = labels.filter((label: string) => { + const isValid = /^[A-Za-z0-9]{1,36}$/.test(label); + if (!isValid) { + console.warn(`Invalid label: "${label}" - must be alphanumeric, 1-36 characters`); + } + return isValid; + }); + + if (validLabels.length !== labels.length) { + throw new Error(`Invalid labels detected. Labels must be alphanumeric (A-Z, a-z, 0-9) and 1-36 characters long. Invalid: ${labels.filter((l: string) => !/^[A-Za-z0-9]{1,36}$/.test(l)).join(', ')}`); + } + + const user = await this.users.updateLabels(userId, validLabels) as any; + + return { + content: [ + { + type: "text", + text: `User ${userId} labels updated:\n${validLabels.join(', ')}` + } + ] + }; + } + + private async getUserPreferences(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId } = args; + + const prefs = await this.users.getPrefs(userId) as any; + + return { + content: [ + { + type: "text", + text: `User ${userId} preferences:\n${JSON.stringify(prefs, null, 2)}` + } + ] + }; + } + + 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` + } + ] + }; + } + + private async listUserIdentities(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId } = args; + + try { + // Try without queries first + let identities; + try { + identities = await this.users.listIdentities(userId) as any; + } catch (error: any) { + if (error.message.includes('queries')) { + // If queries error, try with empty array + identities = await (this.users as any).listIdentities(userId, []); + } else { + throw error; + } + } + + const identityList = identities.identities.map((identity: any) => + `- Provider: ${identity.provider} - ID: ${identity.$id}` + ).join('\n'); + + return { + content: [ + { + type: "text", + text: `User ${userId} identities (${identities.total}):\n${identityList}` + } + ] + }; + } catch (error: any) { + return { + content: [ + { + type: "text", + text: `Error listing identities: ${error.message}` + } + ] + }; + } + } + + private async deleteUserIdentity(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { identityId } = args; + + await this.users.deleteIdentity(identityId); + + return { + content: [ + { + type: "text", + text: `Identity ${identityId} deleted successfully` + } + ] + }; + } + + private async createUserTarget(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, targetId, providerType, identifier, providerId, name } = args; + const tid = targetId || ID.unique(); + + const target = await this.users.createTarget(userId, tid, providerType, identifier, providerId, name) as any; + + return { + content: [ + { + type: "text", + text: `Target created for user ${userId}:\n- ID: ${target.$id}\n- Type: ${target.providerType}\n- Identifier: ${target.identifier}` + } + ] + }; + } + + private async listUserTargets(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId } = args; + + const targets = await this.users.listTargets(userId) as any; + + const targetList = targets.targets.map((target: any) => + `- ${target.name || target.identifier} (${target.providerType}) - ID: ${target.$id}` + ).join('\n'); + + return { + content: [ + { + type: "text", + text: `User ${userId} targets (${targets.total}):\n${targetList}` + } + ] + }; + } + + private async getUserTarget(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, targetId } = args; + + const target = await this.users.getTarget(userId, targetId) as any; + + return { + content: [ + { + type: "text", + text: `User ${userId} target ${targetId}:\n${JSON.stringify(target, null, 2)}` + } + ] + }; + } + + private async updateUserTarget(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, targetId, identifier, providerId, name } = args; + + await this.users.updateTarget(userId, targetId, identifier, providerId, name); + + return { + content: [ + { + type: "text", + text: `User ${userId} target ${targetId} updated successfully` + } + ] + }; + } + + private async deleteUserTarget(args: any) { + if (!this.users) throw new Error("Users service not initialized"); + + const { userId, targetId } = args; + + await this.users.deleteTarget(userId, targetId); + + return { + content: [ + { + type: "text", + text: `User ${userId} target ${targetId} deleted 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 getFileDownload(args: any) { + if (!this.storage) throw new Error("Storage service not initialized"); + + const { bucketId, fileId } = args; + + // For server-side usage, we construct the download URL + const downloadUrl = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/download?project=${this.config?.projectId}`; + + return { + content: [ + { + type: "text", + text: `Download URL for file ${fileId}:\n${downloadUrl}` + } + ] + }; + } + + private async getFilePreview(args: any) { + if (!this.storage) throw new Error("Storage service not initialized"); + + const { bucketId, fileId, width, height, gravity, quality, borderWidth, borderColor, borderRadius, opacity, rotation, background, output } = args; + + // Build query parameters + 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 || ''); + + const previewUrl = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/preview?${params.toString()}`; + + return { + content: [ + { + type: "text", + text: `Preview URL for file ${fileId}:\n${previewUrl}` + } + ] + }; + } + + private async getFileView(args: any) { + if (!this.storage) throw new Error("Storage service not initialized"); + + const { bucketId, fileId } = args; + + const viewUrl = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/view?project=${this.config?.projectId}`; + + return { + content: [ + { + type: "text", + text: `View URL for file ${fileId}:\n${viewUrl}` + } + ] + }; + } + + 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 getFunctionDeployment(args: any) { + if (!this.functions) throw new Error("Functions service not initialized"); + + const { functionId, deploymentId } = args; + + const deployment = await this.functions.getDeployment(functionId, deploymentId) as any; + + return { + content: [ + { + type: "text", + text: `Function ${functionId} deployment ${deploymentId}:\n${JSON.stringify(deployment, null, 2)}` + } + ] + }; + } + + private async updateFunctionDeployment(args: any) { + if (!this.functions) throw new Error("Functions service not initialized"); + + const { functionId, deploymentId, status } = args; + + const deployment = await this.functions.updateDeployment(functionId, deploymentId) as any; + + return { + content: [ + { + type: "text", + text: `Function ${functionId} deployment ${deploymentId} status updated to: ${status}` + } + ] + }; + } + + 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}` + } + ] + }; + } + + // Locale Operations + private async listCountries() { + if (!this.locale) throw new Error("Locale service not initialized"); + + const countries = await this.locale.listCountries() as any; + + const countryList = countries.countries.map((country: any) => { + // Try to find the actual numeric phone code property + const phoneCode = country.phoneCode || country.dialCode || country.countryCode || country.callingCode || country.dialingCode; + return `- ${country.name} (${country.code}) - Phone: +${phoneCode || 'N/A'}`; + }).join('\n'); + + return { + content: [ + { + type: "text", + text: `Countries (${countries.total}):\n${countryList}` + } + ] + }; + } + + private async listContinents() { + if (!this.locale) throw new Error("Locale service not initialized"); + + const continents = await this.locale.listContinents() as any; + + const continentList = continents.continents.map((continent: any) => + `- ${continent.name} (${continent.code})` + ).join('\n'); + + return { + content: [ + { + type: "text", + text: `Continents (${continents.total}):\n${continentList}` + } + ] + }; + } + + private async listCurrencies() { + if (!this.locale) throw new Error("Locale service not initialized"); + + const currencies = await this.locale.listCurrencies() as any; + + const currencyList = currencies.currencies.map((currency: any) => + `- ${currency.name} (${currency.code}) - Symbol: ${currency.symbol}` + ).join('\n'); + + return { + content: [ + { + type: "text", + text: `Currencies (${currencies.total}):\n${currencyList}` + } + ] + }; + } + + private async listLanguages() { + if (!this.locale) throw new Error("Locale service not initialized"); + + const languages = await this.locale.listLanguages() as any; + + const languageList = languages.languages.map((language: any) => + `- ${language.name} (${language.code})` + ).join('\n'); + + return { + content: [ + { + type: "text", + text: `Languages (${languages.total}):\n${languageList}` + } + ] + }; + } + + private async listPhoneCodes() { + if (!this.locale) throw new Error("Locale service not initialized"); + + try { + // Try using the dedicated listPhones API if available + const phones = await (this.locale as any).listPhones() as any; + + if (phones && phones.phones) { + const phoneCodeList = phones.phones.map((phone: any) => + `- ${phone.countryName}: +${phone.countryCode}` + ).join('\n'); + + return { + content: [ + { + type: "text", + text: `Phone codes (${phones.phones.length}):\n${phoneCodeList}` + } + ] + }; + } + } catch (error) { + // Fall back to countries endpoint + } + + // Fallback: Use listCountries + const countries = await this.locale.listCountries() as any; + + // Debug: Check actual structure + if (countries.countries && countries.countries.length > 0) { + const sampleCountry = countries.countries[0]; + const availableProps = Object.keys(sampleCountry).join(', '); + + const phoneCodeList = countries.countries + .map((country: any) => { + const phoneCode = country.phoneCode || country.dialCode || country.countryCode || country.callingCode; + return phoneCode ? `- ${country.name}: +${phoneCode}` : null; + }) + .filter(Boolean) + .join('\n'); + + return { + content: [ + { + type: "text", + text: `Phone codes (${countries.countries.length}):\n${phoneCodeList || 'No phone codes found'}\n\nDebug - Sample country properties: ${availableProps}` + } + ] + }; + } + + return { + content: [ + { + type: "text", + text: `No countries data found. Response structure: ${JSON.stringify(countries, null, 2)}` + } + ] + }; + } + + // Avatars Operations + private async getBrowserIcon(args: any) { + if (!this.avatars) throw new Error("Avatars service not initialized"); + + const { code, width, height, quality } = args; + + const iconUrl = `${this.config?.apiEndpoint}/avatars/browsers/${code}?project=${this.config?.projectId}${width ? `&width=${width}` : ''}${height ? `&height=${height}` : ''}${quality ? `&quality=${quality}` : ''}`; + + return { + content: [ + { + type: "text", + text: `Browser icon URL for ${code}:\n${iconUrl}` + } + ] + }; + } + + private async getCreditCardIcon(args: any) { + if (!this.avatars) throw new Error("Avatars service not initialized"); + + const { code, width, height, quality } = args; + + const iconUrl = `${this.config?.apiEndpoint}/avatars/credit-cards/${code}?project=${this.config?.projectId}${width ? `&width=${width}` : ''}${height ? `&height=${height}` : ''}${quality ? `&quality=${quality}` : ''}`; + + return { + content: [ + { + type: "text", + text: `Credit card icon URL for ${code}:\n${iconUrl}` + } + ] + }; + } + + private async getFavicon(args: any) { + if (!this.avatars) throw new Error("Avatars service not initialized"); + + const { url } = args; + + const faviconUrl = `${this.config?.apiEndpoint}/avatars/favicon?project=${this.config?.projectId}&url=${encodeURIComponent(url)}`; + + return { + content: [ + { + type: "text", + text: `Favicon URL for ${url}:\n${faviconUrl}` + } + ] + }; + } + + private async getFlagIcon(args: any) { + if (!this.avatars) throw new Error("Avatars service not initialized"); + + const { code, width, height, quality } = args; + + const flagUrl = `${this.config?.apiEndpoint}/avatars/flags/${code}?project=${this.config?.projectId}${width ? `&width=${width}` : ''}${height ? `&height=${height}` : ''}${quality ? `&quality=${quality}` : ''}`; + + return { + content: [ + { + type: "text", + text: `Flag icon URL for ${code}:\n${flagUrl}` + } + ] + }; + } + + private async getImageFromUrl(args: any) { + if (!this.avatars) throw new Error("Avatars service not initialized"); + + const { url, width, height } = args; + + const imageUrl = `${this.config?.apiEndpoint}/avatars/image?project=${this.config?.projectId}&url=${encodeURIComponent(url)}${width ? `&width=${width}` : ''}${height ? `&height=${height}` : ''}`; + + return { + content: [ + { + type: "text", + text: `Transformed image URL:\n${imageUrl}` + } + ] + }; + } + + private async getInitialsAvatar(args: any) { + if (!this.avatars) throw new Error("Avatars service not initialized"); + + const { name, width, height, background } = args; + + const avatarUrl = `${this.config?.apiEndpoint}/avatars/initials?project=${this.config?.projectId}&name=${encodeURIComponent(name)}${width ? `&width=${width}` : ''}${height ? `&height=${height}` : ''}${background ? `&background=${background}` : ''}`; + + return { + content: [ + { + type: "text", + text: `Initials avatar URL for "${name}":\n${avatarUrl}` + } + ] + }; + } + + private async getQrCode(args: any) { + if (!this.avatars) throw new Error("Avatars service not initialized"); + + const { text, size, margin, download } = args; + + const qrUrl = `${this.config?.apiEndpoint}/avatars/qr?project=${this.config?.projectId}&text=${encodeURIComponent(text)}${size ? `&size=${size}` : ''}${margin ? `&margin=${margin}` : ''}${download ? `&download=${download}` : ''}`; + + return { + content: [ + { + type: "text", + text: `QR code URL for "${text}":\n${qrUrl}` + } + ] + }; + } + + // 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); \ No newline at end of file