Files
app-write-mcp/src/index.ts
2025-09-05 03:44:25 +03:00

5250 lines
172 KiB
JavaScript

#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from "@modelcontextprotocol/sdk/types.js";
import {
Client,
Databases,
Users,
Storage,
Functions,
Teams,
Account,
Health,
Messaging,
ID,
Query,
Permission,
Role,
IndexType
} from "node-appwrite";
import { InputFile } from "node-appwrite/file";
import { readFileSync, existsSync } from "fs";
import { join } from "path";
import * as dotenv from "dotenv";
interface AppwriteConfig {
projectId: string;
apiEndpoint: string;
apiKey: string;
}
class AppwriteMCPServer {
private server: Server;
private client: Client | null = null;
private databases: Databases | null = null;
private users: Users | null = null;
private storage: Storage | null = null;
private functions: Functions | null = null;
private teams: Teams | null = null;
private account: Account | null = null;
private health: Health | null = null;
private messaging: Messaging | null = null;
private config: AppwriteConfig | null = null;
constructor() {
this.server = new Server(
{
name: "appwrite-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
this.loadConfig();
}
private loadConfig(): void {
// Load environment variables from .env file in current working directory
dotenv.config({ path: join(process.cwd(), '.env') });
const projectId = process.env.APPWRITE_PROJECT_ID;
const apiEndpoint = process.env.APPWRITE_API_ENDPOINT;
const apiKey = process.env.APPWRITE_API_KEY;
if (!projectId || !apiEndpoint || !apiKey) {
console.error("Appwrite configuration missing. Please create a .env file with APPWRITE_PROJECT_ID, APPWRITE_API_ENDPOINT, and APPWRITE_API_KEY");
return;
}
this.config = {
projectId,
apiEndpoint,
apiKey
};
this.initializeAppwrite();
}
private initializeAppwrite(): void {
if (!this.config) return;
this.client = new Client()
.setEndpoint(this.config.apiEndpoint)
.setProject(this.config.projectId)
.setKey(this.config.apiKey);
this.databases = new Databases(this.client);
this.users = new Users(this.client);
this.storage = new Storage(this.client);
this.functions = new Functions(this.client);
this.teams = new Teams(this.client);
this.account = new Account(this.client);
this.health = new Health(this.client);
this.messaging = new Messaging(this.client);
}
private setupToolHandlers(): void {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
// Database Operations
{
name: "create_database",
description: "Create a new database in Appwrite",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "Unique ID for the database (optional)" },
name: { type: "string", description: "Name of the database" }
},
required: ["name"]
}
},
{
name: "list_databases",
description: "List all databases in the Appwrite project",
inputSchema: { type: "object", properties: {} }
},
{
name: "delete_database",
description: "Delete a database from Appwrite",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database to delete" }
},
required: ["databaseId"]
}
},
// Collection Operations
{
name: "create_collection",
description: "Create a new collection in a database",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "Unique ID for the collection (optional)" },
name: { type: "string", description: "Name of the collection" },
permissions: { type: "array", description: "Collection permissions (optional)", items: { type: "string" } }
},
required: ["databaseId", "name"]
}
},
{
name: "list_collections",
description: "List all collections in a database",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" }
},
required: ["databaseId"]
}
},
{
name: "update_collection",
description: "Update collection name and permissions",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
name: { type: "string", description: "New collection name" },
permissions: { type: "array", description: "Collection permissions (optional)", items: { type: "string" } }
},
required: ["databaseId", "collectionId", "name"]
}
},
{
name: "delete_collection",
description: "Delete a collection from a database",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection to delete" }
},
required: ["databaseId", "collectionId"]
}
},
// Attribute Operations
{
name: "create_attribute",
description: "Create any type of attribute in a collection (string, integer, float, boolean, datetime, email, IP, URL, enum, relationship)",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
key: { type: "string", description: "Attribute key" },
type: {
type: "string",
description: "Attribute type",
enum: ["string", "integer", "float", "boolean", "datetime", "email", "ip", "url", "enum", "relationship"]
},
required: { type: "boolean", description: "Is attribute required" },
default: { type: "string", description: "Default value (optional)" },
size: { type: "number", description: "Size for string/ip/url attributes (optional)" },
min: { type: "number", description: "Minimum value for integer/float attributes (optional)" },
max: { type: "number", description: "Maximum value for integer/float attributes (optional)" },
elements: {
type: "array",
items: { type: "string" },
description: "Array of allowed values for enum attributes (required for enum type)"
},
relatedCollection: { type: "string", description: "Related collection ID for relationship attributes (required for relationship type)" },
relationType: {
type: "string",
description: "Relationship type (required for relationship type)",
enum: ["oneToOne", "oneToMany", "manyToOne", "manyToMany"]
},
twoWay: { type: "boolean", description: "Is relationship two-way (optional for relationship type)" },
twoWayKey: { type: "string", description: "Two-way relationship key (optional for relationship type)" }
},
required: ["databaseId", "collectionId", "key", "type", "required"]
}
},
{
name: "bulk_create_attributes",
description: "Create multiple attributes at once in a collection",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
attributes: {
type: "array",
description: "Array of attribute definitions",
items: {
type: "object",
properties: {
key: { type: "string", description: "Attribute key" },
type: {
type: "string",
description: "Attribute type",
enum: ["string", "integer", "float", "boolean", "datetime", "email", "ip", "url", "enum", "relationship"]
},
required: { type: "boolean", description: "Is attribute required" },
default: { type: "string", description: "Default value (optional)" },
size: { type: "number", description: "Size for string/ip/url attributes (optional)" },
min: { type: "number", description: "Minimum value for integer/float attributes (optional)" },
max: { type: "number", description: "Maximum value for integer/float attributes (optional)" },
elements: {
type: "array",
items: { type: "string" },
description: "Array of allowed values for enum attributes (required for enum type)"
},
relatedCollection: { type: "string", description: "Related collection ID for relationship attributes (required for relationship type)" },
relationType: {
type: "string",
description: "Relationship type (required for relationship type)",
enum: ["oneToOne", "oneToMany", "manyToOne", "manyToMany"]
},
twoWay: { type: "boolean", description: "Is relationship two-way (optional for relationship type)" },
twoWayKey: { type: "string", description: "Two-way relationship key (optional for relationship type)" }
},
required: ["key", "type", "required"]
}
}
},
required: ["databaseId", "collectionId", "attributes"]
}
},
{
name: "bulk_delete_attributes",
description: "Delete multiple attributes at once from a collection",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
attributeKeys: {
type: "array",
description: "Array of attribute keys to delete",
items: { type: "string" }
}
},
required: ["databaseId", "collectionId", "attributeKeys"]
}
},
{
name: "list_attributes",
description: "List all attributes in a collection",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" }
},
required: ["databaseId", "collectionId"]
}
},
{
name: "delete_attribute",
description: "Delete an attribute from a collection",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
key: { type: "string", description: "Attribute key to delete" }
},
required: ["databaseId", "collectionId", "key"]
}
},
{
name: "get_attribute",
description: "Get details of a specific attribute",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
key: { type: "string", description: "Attribute key" }
},
required: ["databaseId", "collectionId", "key"]
}
},
{
name: "update_attribute",
description: "Update any type of attribute (string, integer, float, boolean, datetime, email, IP, URL, enum, relationship)",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
key: { type: "string", description: "Attribute key" },
type: {
type: "string",
description: "Attribute type",
enum: ["string", "integer", "float", "boolean", "datetime", "email", "ip", "url", "enum", "relationship"]
},
required: { type: "boolean", description: "Is attribute required" },
default: { type: "string", description: "Default value (optional)" },
size: { type: "number", description: "Size for string/ip/url attributes (optional)" },
min: { type: "number", description: "Minimum value for integer/float attributes (optional)" },
max: { type: "number", description: "Maximum value for integer/float attributes (optional)" },
elements: {
type: "array",
items: { type: "string" },
description: "Array of allowed values for enum attributes (required for enum type)"
}
},
required: ["databaseId", "collectionId", "key", "type", "required"]
}
},
{
name: "get_collection",
description: "Get details of a specific collection",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" }
},
required: ["databaseId", "collectionId"]
}
},
// Index Operations
{
name: "create_index",
description: "Create an index in a collection",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
key: { type: "string", description: "Index key" },
type: { type: "string", description: "Index type (key, fulltext, unique)", enum: ["key", "fulltext", "unique"] },
attributes: { type: "array", description: "Array of attribute keys", items: { type: "string" } }
},
required: ["databaseId", "collectionId", "key", "type", "attributes"]
}
},
{
name: "list_indexes",
description: "List all indexes in a collection",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" }
},
required: ["databaseId", "collectionId"]
}
},
{
name: "delete_index",
description: "Delete an index from a collection",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
key: { type: "string", description: "Index key to delete" }
},
required: ["databaseId", "collectionId", "key"]
}
},
{
name: "get_index",
description: "Get details of a specific index",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
key: { type: "string", description: "Index key" }
},
required: ["databaseId", "collectionId", "key"]
}
},
// Document Operations
{
name: "create_document",
description: "Create a new document in a collection",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
documentId: { type: "string", description: "Unique ID for the document (optional)" },
data: { type: "object", description: "Document data as JSON object" },
permissions: { type: "array", description: "Document permissions (optional)", items: { type: "string" } }
},
required: ["databaseId", "collectionId", "data"]
}
},
{
name: "get_document",
description: "Get a document by ID",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
documentId: { type: "string", description: "ID of the document" }
},
required: ["databaseId", "collectionId", "documentId"]
}
},
{
name: "list_documents",
description: "List documents in a collection with optional queries, limit, and offset",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } },
limit: { type: "number", description: "Maximum number of documents to return (default 25, max 5000)" },
offset: { type: "number", description: "Number of documents to skip for pagination (default 0)" }
},
required: ["databaseId", "collectionId"]
}
},
{
name: "update_document",
description: "Update a document",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
documentId: { type: "string", description: "ID of the document" },
data: { type: "object", description: "Updated document data" },
permissions: { type: "array", description: "Document permissions (optional)", items: { type: "string" } }
},
required: ["databaseId", "collectionId", "documentId", "data"]
}
},
{
name: "delete_document",
description: "Delete a document",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
documentId: { type: "string", description: "ID of the document" }
},
required: ["databaseId", "collectionId", "documentId"]
}
},
// User Management Operations
{
name: "create_user",
description: "Create a new user",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "Unique user ID (optional)" },
email: { type: "string", description: "User email" },
phone: { type: "string", description: "User phone number in international format (optional, e.g., '+1234567890')" },
password: { type: "string", description: "User password" },
name: { type: "string", description: "User name (optional)" }
},
required: ["email", "password"]
}
},
{
name: "list_users",
description: "List all users with pagination",
inputSchema: {
type: "object",
properties: {
queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } },
limit: { type: "number", description: "Maximum number of users to return (default 25, max 5000)" },
offset: { type: "number", description: "Number of users to skip for pagination (default 0)" }
}
}
},
{
name: "get_user",
description: "Get a user by ID",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "User ID" }
},
required: ["userId"]
}
},
{
name: "update_user",
description: "Update user email, name, and/or password",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "User ID" },
email: { type: "string", description: "New email (optional)" },
name: { type: "string", description: "New name (optional)" },
password: { type: "string", description: "New password (optional)" }
},
required: ["userId"]
}
},
{
name: "delete_user",
description: "Delete a user",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "User ID" }
},
required: ["userId"]
}
},
{
name: "update_user_preferences",
description: "Update user preferences",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "User ID" },
prefs: { type: "object", description: "User preferences object" }
},
required: ["userId", "prefs"]
}
},
// Storage Operations
{
name: "create_bucket",
description: "Create a storage bucket",
inputSchema: {
type: "object",
properties: {
bucketId: { type: "string", description: "Unique bucket ID (optional)" },
name: { type: "string", description: "Bucket name" },
permissions: { type: "array", description: "Bucket permissions (optional)", items: { type: "string" } },
fileSecurity: { type: "boolean", description: "Enable file security (optional)" },
enabled: { type: "boolean", description: "Enable bucket (optional, default true)" }
},
required: ["name"]
}
},
{
name: "list_buckets",
description: "List all storage buckets",
inputSchema: { type: "object", properties: {} }
},
{
name: "get_bucket",
description: "Get a bucket by ID",
inputSchema: {
type: "object",
properties: {
bucketId: { type: "string", description: "Bucket ID" }
},
required: ["bucketId"]
}
},
{
name: "update_bucket",
description: "Update a storage bucket",
inputSchema: {
type: "object",
properties: {
bucketId: { type: "string", description: "Bucket ID" },
name: { type: "string", description: "Bucket name" },
permissions: { type: "array", description: "Bucket permissions (optional)", items: { type: "string" } },
fileSecurity: { type: "boolean", description: "Enable file security (optional)" },
enabled: { type: "boolean", description: "Enable bucket (optional)" }
},
required: ["bucketId", "name"]
}
},
{
name: "delete_bucket",
description: "Delete a storage bucket",
inputSchema: {
type: "object",
properties: {
bucketId: { type: "string", description: "Bucket ID" }
},
required: ["bucketId"]
}
},
{
name: "list_files",
description: "List files in a storage bucket",
inputSchema: {
type: "object",
properties: {
bucketId: { type: "string", description: "Bucket ID" }
},
required: ["bucketId"]
}
},
{
name: "get_file",
description: "Get file details by ID",
inputSchema: {
type: "object",
properties: {
bucketId: { type: "string", description: "Bucket ID" },
fileId: { type: "string", description: "File ID" }
},
required: ["bucketId", "fileId"]
}
},
{
name: "get_file_url",
description: "Get file URL (download, preview with transformations, or view)",
inputSchema: {
type: "object",
properties: {
bucketId: { type: "string", description: "Bucket ID" },
fileId: { type: "string", description: "File ID" },
type: { type: "string", enum: ["download", "preview", "view"], description: "URL type: download, preview, or view" },
width: { type: "number", description: "Preview width (optional, only for preview type)" },
height: { type: "number", description: "Preview height (optional, only for preview type)" },
gravity: { type: "string", description: "Image gravity (optional, only for preview type)" },
quality: { type: "number", description: "Image quality 0-100 (optional, only for preview type)" },
borderWidth: { type: "number", description: "Border width (optional, only for preview type)" },
borderColor: { type: "string", description: "Border color (optional, only for preview type)" },
borderRadius: { type: "number", description: "Border radius (optional, only for preview type)" },
opacity: { type: "number", description: "Opacity 0-1 (optional, only for preview type)" },
rotation: { type: "number", description: "Rotation in degrees (optional, only for preview type)" },
background: { type: "string", description: "Background color (optional, only for preview type)" },
output: { type: "string", description: "Output format (optional, only for preview type)" }
},
required: ["bucketId", "fileId", "type"]
}
},
{
name: "delete_file",
description: "Delete a file",
inputSchema: {
type: "object",
properties: {
bucketId: { type: "string", description: "Bucket ID" },
fileId: { type: "string", description: "File ID" }
},
required: ["bucketId", "fileId"]
}
},
{
name: "create_file",
description: "Upload a file to storage bucket",
inputSchema: {
type: "object",
properties: {
bucketId: { type: "string", description: "Bucket ID" },
fileId: { type: "string", description: "File ID (optional)" },
filePath: { type: "string", description: "Local file path to upload" },
permissions: { type: "array", description: "File permissions (optional)", items: { type: "string" } }
},
required: ["bucketId", "filePath"]
}
},
{
name: "update_file",
description: "Update file metadata",
inputSchema: {
type: "object",
properties: {
bucketId: { type: "string", description: "Bucket ID" },
fileId: { type: "string", description: "File ID" },
name: { type: "string", description: "File name (optional)" },
permissions: { type: "array", description: "File permissions (optional)", items: { type: "string" } }
},
required: ["bucketId", "fileId"]
}
},
// Function Operations
{
name: "create_function",
description: "Create a new function",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Unique function ID (optional)" },
name: { type: "string", description: "Function name" },
runtime: { type: "string", description: "Function runtime (e.g., node-18.0, python-3.9)" },
execute: { type: "array", description: "Execute permissions (optional)", items: { type: "string" } },
events: { type: "array", description: "Events that trigger the function (optional)", items: { type: "string" } },
schedule: { type: "string", description: "CRON schedule (optional)" },
timeout: { type: "number", description: "Timeout in seconds (optional, max 900)" }
},
required: ["name", "runtime"]
}
},
{
name: "list_functions",
description: "List all functions",
inputSchema: { type: "object", properties: {} }
},
{
name: "get_function",
description: "Get a function by ID",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" }
},
required: ["functionId"]
}
},
{
name: "update_function",
description: "Update a function",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" },
name: { type: "string", description: "Function name" },
runtime: { type: "string", description: "Function runtime (optional)" },
execute: { type: "array", description: "Execute permissions (optional)", items: { type: "string" } },
events: { type: "array", description: "Events that trigger the function (optional)", items: { type: "string" } },
schedule: { type: "string", description: "CRON schedule (optional)" },
timeout: { type: "number", description: "Timeout in seconds (optional)" }
},
required: ["functionId", "name"]
}
},
{
name: "delete_function",
description: "Delete a function",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" }
},
required: ["functionId"]
}
},
{
name: "list_function_deployments",
description: "List function deployments",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" },
queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } },
search: { type: "string", description: "Search term (optional)" }
},
required: ["functionId"]
}
},
{
name: "delete_function_deployment",
description: "Delete function deployment",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" },
deploymentId: { type: "string", description: "Deployment ID" }
},
required: ["functionId", "deploymentId"]
}
},
{
name: "create_function_variable",
description: "Create function variable",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" },
key: { type: "string", description: "Variable key" },
value: { type: "string", description: "Variable value" },
secret: { type: "boolean", description: "Is secret variable (optional)" }
},
required: ["functionId", "key", "value"]
}
},
{
name: "list_function_variables",
description: "List function variables",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" }
},
required: ["functionId"]
}
},
{
name: "get_function_variable",
description: "Get function variable by ID",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" },
variableId: { type: "string", description: "Variable ID" }
},
required: ["functionId", "variableId"]
}
},
{
name: "update_function_variable",
description: "Update function variable",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" },
variableId: { type: "string", description: "Variable ID" },
key: { type: "string", description: "Variable key" },
value: { type: "string", description: "Variable value (optional)" },
secret: { type: "boolean", description: "Is secret variable (optional)" }
},
required: ["functionId", "variableId", "key"]
}
},
{
name: "delete_function_variable",
description: "Delete function variable",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" },
variableId: { type: "string", description: "Variable ID" }
},
required: ["functionId", "variableId"]
}
},
// Team Operations
{
name: "create_team",
description: "Create a new team",
inputSchema: {
type: "object",
properties: {
teamId: { type: "string", description: "Unique team ID (optional)" },
name: { type: "string", description: "Team name" },
roles: { type: "array", description: "Team roles (optional)", items: { type: "string" } }
},
required: ["name"]
}
},
{
name: "list_teams",
description: "List all teams",
inputSchema: { type: "object", properties: {} }
},
{
name: "get_team",
description: "Get a team by ID",
inputSchema: {
type: "object",
properties: {
teamId: { type: "string", description: "Team ID" }
},
required: ["teamId"]
}
},
{
name: "update_team",
description: "Update a team",
inputSchema: {
type: "object",
properties: {
teamId: { type: "string", description: "Team ID" },
name: { type: "string", description: "Team name" }
},
required: ["teamId", "name"]
}
},
{
name: "delete_team",
description: "Delete a team",
inputSchema: {
type: "object",
properties: {
teamId: { type: "string", description: "Team ID" }
},
required: ["teamId"]
}
},
// Messaging Operations
{
name: "create_messaging_provider",
description: "Create a messaging provider",
inputSchema: {
type: "object",
properties: {
providerId: { type: "string", description: "Provider ID (optional)" },
name: { type: "string", description: "Provider name" },
type: { type: "string", description: "Provider type (email, sms, push)" },
enabled: { type: "boolean", description: "Enable provider (optional)" }
},
required: ["name", "type"]
}
},
{
name: "list_messaging_providers",
description: "List messaging providers",
inputSchema: { type: "object", properties: {} }
},
{
name: "get_messaging_provider",
description: "Get messaging provider by ID",
inputSchema: {
type: "object",
properties: {
providerId: { type: "string", description: "Provider ID" }
},
required: ["providerId"]
}
},
{
name: "update_messaging_provider",
description: "Update messaging provider",
inputSchema: {
type: "object",
properties: {
providerId: { type: "string", description: "Provider ID" },
name: { type: "string", description: "Provider name (optional)" },
enabled: { type: "boolean", description: "Enable provider (optional)" }
},
required: ["providerId"]
}
},
{
name: "delete_messaging_provider",
description: "Delete messaging provider",
inputSchema: {
type: "object",
properties: {
providerId: { type: "string", description: "Provider ID" }
},
required: ["providerId"]
}
},
{
name: "create_messaging_message",
description: "Create and send a message",
inputSchema: {
type: "object",
properties: {
messageId: { type: "string", description: "Message ID (optional)" },
subject: { type: "string", description: "Message subject (optional)" },
content: { type: "string", description: "Message content" },
topics: { type: "array", description: "Topic IDs (optional)", items: { type: "string" } },
users: { type: "array", description: "User IDs (optional)", items: { type: "string" } },
targets: { type: "array", description: "Target IDs (optional)", items: { type: "string" } }
},
required: ["content"]
}
},
{
name: "list_messaging_messages",
description: "List messaging messages",
inputSchema: {
type: "object",
properties: {
queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } }
}
}
},
{
name: "get_messaging_message",
description: "Get messaging message by ID",
inputSchema: {
type: "object",
properties: {
messageId: { type: "string", description: "Message ID" }
},
required: ["messageId"]
}
},
{
name: "update_messaging_message",
description: "Update messaging message",
inputSchema: {
type: "object",
properties: {
messageId: { type: "string", description: "Message ID" },
topics: { type: "array", description: "Topic IDs (optional)", items: { type: "string" } },
users: { type: "array", description: "User IDs (optional)", items: { type: "string" } },
targets: { type: "array", description: "Target IDs (optional)", items: { type: "string" } },
status: { type: "string", description: "Message status (optional)" }
},
required: ["messageId"]
}
},
{
name: "delete_messaging_message",
description: "Delete messaging message",
inputSchema: {
type: "object",
properties: {
messageId: { type: "string", description: "Message ID" }
},
required: ["messageId"]
}
},
{
name: "create_messaging_topic",
description: "Create messaging topic",
inputSchema: {
type: "object",
properties: {
topicId: { type: "string", description: "Topic ID (optional)" },
name: { type: "string", description: "Topic name" },
description: { type: "string", description: "Topic description (optional)" }
},
required: ["name"]
}
},
{
name: "list_messaging_topics",
description: "List messaging topics",
inputSchema: {
type: "object",
properties: {
queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } }
}
}
},
{
name: "get_messaging_topic",
description: "Get messaging topic by ID",
inputSchema: {
type: "object",
properties: {
topicId: { type: "string", description: "Topic ID" }
},
required: ["topicId"]
}
},
{
name: "update_messaging_topic",
description: "Update messaging topic",
inputSchema: {
type: "object",
properties: {
topicId: { type: "string", description: "Topic ID" },
name: { type: "string", description: "Topic name (optional)" },
description: { type: "string", description: "Topic description (optional)" }
},
required: ["topicId"]
}
},
{
name: "delete_messaging_topic",
description: "Delete messaging topic",
inputSchema: {
type: "object",
properties: {
topicId: { type: "string", description: "Topic ID" }
},
required: ["topicId"]
}
},
{
name: "create_messaging_subscriber",
description: "Create messaging topic subscriber",
inputSchema: {
type: "object",
properties: {
topicId: { type: "string", description: "Topic ID" },
subscriberId: { type: "string", description: "Subscriber ID (optional)" },
targetId: { type: "string", description: "Target ID" }
},
required: ["topicId", "targetId"]
}
},
{
name: "list_messaging_subscribers",
description: "List messaging topic subscribers",
inputSchema: {
type: "object",
properties: {
topicId: { type: "string", description: "Topic ID" },
queries: { type: "array", description: "Query filters (optional)", items: { type: "string" } }
},
required: ["topicId"]
}
},
{
name: "get_messaging_subscriber",
description: "Get messaging subscriber by ID",
inputSchema: {
type: "object",
properties: {
topicId: { type: "string", description: "Topic ID" },
subscriberId: { type: "string", description: "Subscriber ID" }
},
required: ["topicId", "subscriberId"]
}
},
{
name: "delete_messaging_subscriber",
description: "Delete messaging subscriber",
inputSchema: {
type: "object",
properties: {
topicId: { type: "string", description: "Topic ID" },
subscriberId: { type: "string", description: "Subscriber ID" }
},
required: ["topicId", "subscriberId"]
}
},
// Function Execution
{
name: "list_executions",
description: "List function executions with logs",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" },
limit: { type: "number", description: "Number of executions to return (optional, max 100)", default: 25 }
},
required: ["functionId"]
}
},
{
name: "get_execution",
description: "Get details of a specific function execution",
inputSchema: {
type: "object",
properties: {
functionId: { type: "string", description: "Function ID" },
executionId: { type: "string", description: "Execution ID" }
},
required: ["functionId", "executionId"]
}
},
// Health Monitoring
{
name: "get_health",
description: "Get overall health status of Appwrite services",
inputSchema: {
type: "object",
properties: {}
}
},
// Bulk Operations
{
name: "bulk_create_users",
description: "Create multiple users at once",
inputSchema: {
type: "object",
properties: {
users: {
type: "array",
description: "Array of user objects to create",
items: {
type: "object",
properties: {
userId: { type: "string", description: "User ID (optional)" },
email: { type: "string", description: "User email" },
phone: { type: "string", description: "User phone number (optional)" },
password: { type: "string", description: "User password" },
name: { type: "string", description: "User name (optional)" }
},
required: ["email", "password"]
}
}
},
required: ["users"]
}
},
{
name: "bulk_update_users",
description: "Update multiple users at once",
inputSchema: {
type: "object",
properties: {
updates: {
type: "array",
description: "Array of user updates",
items: {
type: "object",
properties: {
userId: { type: "string", description: "User ID" },
email: { type: "string", description: "New email (optional)" },
name: { type: "string", description: "New name (optional)" },
password: { type: "string", description: "New password (optional)" }
},
required: ["userId"]
}
}
},
required: ["updates"]
}
},
{
name: "bulk_delete_users",
description: "Delete multiple users at once",
inputSchema: {
type: "object",
properties: {
userIds: {
type: "array",
description: "Array of user IDs to delete",
items: { type: "string" }
}
},
required: ["userIds"]
}
},
{
name: "bulk_create_documents",
description: "Create multiple documents at once",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
documents: {
type: "array",
description: "Array of documents to create",
items: {
type: "object",
properties: {
documentId: { type: "string", description: "Document ID (optional)" },
data: { type: "object", description: "Document data" },
permissions: { type: "array", description: "Document permissions (optional)", items: { type: "string" } }
},
required: ["data"]
}
}
},
required: ["databaseId", "collectionId", "documents"]
}
},
{
name: "bulk_update_documents",
description: "Update multiple documents at once",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
updates: {
type: "array",
description: "Array of document updates",
items: {
type: "object",
properties: {
documentId: { type: "string", description: "Document ID" },
data: { type: "object", description: "Updated document data" },
permissions: { type: "array", description: "Document permissions (optional)", items: { type: "string" } }
},
required: ["documentId", "data"]
}
}
},
required: ["databaseId", "collectionId", "updates"]
}
},
{
name: "bulk_delete_documents",
description: "Delete multiple documents at once",
inputSchema: {
type: "object",
properties: {
databaseId: { type: "string", description: "ID of the database" },
collectionId: { type: "string", description: "ID of the collection" },
documentIds: {
type: "array",
description: "Array of document IDs to delete",
items: { type: "string" }
}
},
required: ["databaseId", "collectionId", "documentIds"]
}
}
]
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (!this.config || !this.databases) {
throw new McpError(
ErrorCode.InternalError,
"Appwrite not configured. Please set environment variables: APPWRITE_PROJECT_ID, APPWRITE_API_ENDPOINT, and APPWRITE_API_KEY. For Claude Code CLI, create a .env file in your working directory. For Cursor IDE, add them to your MCP server configuration in settings.json."
);
}
try {
switch (request.params.name) {
// Database Operations
case "create_database":
return await this.createDatabase(request.params.arguments);
case "list_databases":
return await this.listDatabases();
case "delete_database":
return await this.deleteDatabase(request.params.arguments);
// Collection Operations
case "create_collection":
return await this.createCollection(request.params.arguments);
case "list_collections":
return await this.listCollections(request.params.arguments);
case "update_collection":
return await this.updateCollection(request.params.arguments);
case "delete_collection":
return await this.deleteCollection(request.params.arguments);
// Attribute Operations
case "create_attribute":
return await this.createAttribute(request.params.arguments);
case "bulk_create_attributes":
return await this.bulkCreateAttributes(request.params.arguments);
case "bulk_delete_attributes":
return await this.bulkDeleteAttributes(request.params.arguments);
case "list_attributes":
return await this.listAttributes(request.params.arguments);
case "delete_attribute":
return await this.deleteAttribute(request.params.arguments);
case "get_attribute":
return await this.getAttribute(request.params.arguments);
case "update_attribute":
return await this.updateAttribute(request.params.arguments);
case "get_collection":
return await this.getCollection(request.params.arguments);
// Index Operations
case "create_index":
return await this.createIndex(request.params.arguments);
case "list_indexes":
return await this.listIndexes(request.params.arguments);
case "delete_index":
return await this.deleteIndex(request.params.arguments);
case "get_index":
return await this.getIndex(request.params.arguments);
// Document Operations
case "create_document":
return await this.createDocument(request.params.arguments);
case "get_document":
return await this.getDocument(request.params.arguments);
case "list_documents":
return await this.listDocuments(request.params.arguments);
case "update_document":
return await this.updateDocument(request.params.arguments);
case "delete_document":
return await this.deleteDocument(request.params.arguments);
// User Management Operations
case "create_user":
return await this.createUser(request.params.arguments);
case "list_users":
return await this.listUsers(request.params.arguments);
case "get_user":
return await this.getUser(request.params.arguments);
case "update_user":
return await this.updateUser(request.params.arguments);
case "delete_user":
return await this.deleteUser(request.params.arguments);
case "update_user_preferences":
return await this.updateUserPreferences(request.params.arguments);
// Storage Operations
case "create_bucket":
return await this.createBucket(request.params.arguments);
case "list_buckets":
return await this.listBuckets();
case "get_bucket":
return await this.getBucket(request.params.arguments);
case "update_bucket":
return await this.updateBucket(request.params.arguments);
case "delete_bucket":
return await this.deleteBucket(request.params.arguments);
case "list_files":
return await this.listFiles(request.params.arguments);
case "get_file":
return await this.getFile(request.params.arguments);
case "get_file_url":
return await this.getFileUrl(request.params.arguments);
case "delete_file":
return await this.deleteFile(request.params.arguments);
case "create_file":
return await this.createFile(request.params.arguments);
case "update_file":
return await this.updateFile(request.params.arguments);
// Function Operations
case "create_function":
return await this.createFunction(request.params.arguments);
case "list_functions":
return await this.listFunctions();
case "get_function":
return await this.getFunction(request.params.arguments);
case "update_function":
return await this.updateFunction(request.params.arguments);
case "delete_function":
return await this.deleteFunction(request.params.arguments);
case "list_function_deployments":
return await this.listFunctionDeployments(request.params.arguments);
case "delete_function_deployment":
return await this.deleteFunctionDeployment(request.params.arguments);
case "create_function_variable":
return await this.createFunctionVariable(request.params.arguments);
case "list_function_variables":
return await this.listFunctionVariables(request.params.arguments);
case "get_function_variable":
return await this.getFunctionVariable(request.params.arguments);
case "update_function_variable":
return await this.updateFunctionVariable(request.params.arguments);
case "delete_function_variable":
return await this.deleteFunctionVariable(request.params.arguments);
// Team Operations
case "create_team":
return await this.createTeam(request.params.arguments);
case "list_teams":
return await this.listTeams();
case "get_team":
return await this.getTeam(request.params.arguments);
case "update_team":
return await this.updateTeam(request.params.arguments);
case "delete_team":
return await this.deleteTeam(request.params.arguments);
// Messaging Operations
case "create_messaging_provider":
return await this.createMessagingProvider(request.params.arguments);
case "list_messaging_providers":
return await this.listMessagingProviders();
case "get_messaging_provider":
return await this.getMessagingProvider(request.params.arguments);
case "update_messaging_provider":
return await this.updateMessagingProvider(request.params.arguments);
case "delete_messaging_provider":
return await this.deleteMessagingProvider(request.params.arguments);
case "create_messaging_message":
return await this.createMessagingMessage(request.params.arguments);
case "list_messaging_messages":
return await this.listMessagingMessages(request.params.arguments);
case "get_messaging_message":
return await this.getMessagingMessage(request.params.arguments);
case "update_messaging_message":
return await this.updateMessagingMessage(request.params.arguments);
case "delete_messaging_message":
return await this.deleteMessagingMessage(request.params.arguments);
case "create_messaging_topic":
return await this.createMessagingTopic(request.params.arguments);
case "list_messaging_topics":
return await this.listMessagingTopics(request.params.arguments);
case "get_messaging_topic":
return await this.getMessagingTopic(request.params.arguments);
case "update_messaging_topic":
return await this.updateMessagingTopic(request.params.arguments);
case "delete_messaging_topic":
return await this.deleteMessagingTopic(request.params.arguments);
case "create_messaging_subscriber":
return await this.createMessagingSubscriber(request.params.arguments);
case "list_messaging_subscribers":
return await this.listMessagingSubscribers(request.params.arguments);
case "get_messaging_subscriber":
return await this.getMessagingSubscriber(request.params.arguments);
case "delete_messaging_subscriber":
return await this.deleteMessagingSubscriber(request.params.arguments);
// Function Execution
case "list_executions":
return await this.listExecutions(request.params.arguments);
case "get_execution":
return await this.getExecution(request.params.arguments);
// Health Monitoring
case "get_health":
return await this.getHealth();
// Bulk Operations
case "bulk_create_users":
return await this.bulkCreateUsers(request.params.arguments);
case "bulk_update_users":
return await this.bulkUpdateUsers(request.params.arguments);
case "bulk_delete_users":
return await this.bulkDeleteUsers(request.params.arguments);
case "bulk_create_documents":
return await this.bulkCreateDocuments(request.params.arguments);
case "bulk_update_documents":
return await this.bulkUpdateDocuments(request.params.arguments);
case "bulk_delete_documents":
return await this.bulkDeleteDocuments(request.params.arguments);
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Appwrite operation failed: ${error instanceof Error ? error.message : String(error)}`
);
}
});
}
private async createDatabase(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const databaseId = args.databaseId || ID.unique();
const name = args.name;
const database = await this.databases.create(databaseId, name);
return {
content: [
{
type: "text",
text: `Database created successfully:\n- ID: ${database.$id}\n- Name: ${database.name}\n- Created: ${database.$createdAt}`
}
]
};
}
private async listDatabases() {
if (!this.databases) throw new Error("Databases not initialized");
const databases = await this.databases.list();
const databaseList = databases.databases.map(db =>
`- ${db.name} (ID: ${db.$id})`
).join('\n');
return {
content: [
{
type: "text",
text: `Databases (${databases.total}):\n${databaseList}`
}
]
};
}
private async deleteDatabase(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const databaseId = args.databaseId;
await this.databases.delete(databaseId);
return {
content: [
{
type: "text",
text: `Database ${databaseId} deleted successfully`
}
]
};
}
private async createCollection(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const databaseId = args.databaseId;
const collectionId = args.collectionId || ID.unique();
const name = args.name;
const collection = await this.databases.createCollection(databaseId, collectionId, name);
return {
content: [
{
type: "text",
text: `Collection created successfully:\n- ID: ${collection.$id}\n- Name: ${collection.name}\n- Database: ${collection.databaseId}\n- Created: ${collection.$createdAt}`
}
]
};
}
private async listCollections(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const databaseId = args.databaseId;
const collections = await this.databases.listCollections(databaseId);
const collectionList = collections.collections.map(col =>
`- ${col.name} (ID: ${col.$id})`
).join('\n');
return {
content: [
{
type: "text",
text: `Collections in database ${databaseId} (${collections.total}):\n${collectionList}`
}
]
};
}
private async updateCollection(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, name, permissions } = args;
const collection = await this.databases.updateCollection(
databaseId,
collectionId,
name,
permissions
) as any;
return {
content: [
{
type: "text",
text: `Collection updated:\n- ID: ${collection.$id}\n- Name: ${collection.name}\n- Documents: ${collection.total}\n- Permissions: ${collection.$permissions?.join(', ') || 'None'}`
}
]
};
}
private async deleteCollection(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const databaseId = args.databaseId;
const collectionId = args.collectionId;
await this.databases.deleteCollection(databaseId, collectionId);
return {
content: [
{
type: "text",
text: `Collection ${collectionId} deleted successfully from database ${databaseId}`
}
]
};
}
// Attribute Operations
private async createAttribute(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const {
databaseId,
collectionId,
key,
type,
required,
default: defaultValue,
size,
min,
max,
elements,
relatedCollection,
relationType,
twoWay,
twoWayKey
} = args;
let attribute;
switch (type) {
case 'string':
attribute = await this.databases.createStringAttribute(
databaseId, collectionId, key, size || 255, required, defaultValue
);
break;
case 'integer':
attribute = await this.databases.createIntegerAttribute(
databaseId, collectionId, key, required, min, max, defaultValue
);
break;
case 'float':
attribute = await this.databases.createFloatAttribute(
databaseId, collectionId, key, required, min, max, defaultValue
);
break;
case 'boolean':
attribute = await this.databases.createBooleanAttribute(
databaseId, collectionId, key, required, defaultValue
);
break;
case 'datetime':
attribute = await this.databases.createDatetimeAttribute(
databaseId, collectionId, key, required, defaultValue
);
break;
case 'email':
attribute = await this.databases.createEmailAttribute(
databaseId, collectionId, key, required, defaultValue
);
break;
case 'ip':
attribute = await this.databases.createIpAttribute(
databaseId, collectionId, key, required, defaultValue
);
break;
case 'url':
attribute = await this.databases.createUrlAttribute(
databaseId, collectionId, key, required, defaultValue
);
break;
case 'enum':
if (!elements || elements.length === 0) {
throw new Error("Enum attributes require 'elements' array with allowed values");
}
attribute = await this.databases.createEnumAttribute(
databaseId, collectionId, key, elements, required, defaultValue
);
break;
case 'relationship':
if (!relatedCollection || !relationType) {
throw new Error("Relationship attributes require 'relatedCollection' and 'relationType'");
}
attribute = await this.databases.createRelationshipAttribute(
databaseId, collectionId, relatedCollection, relationType, twoWay, key, twoWayKey
);
break;
default:
throw new Error(`Unsupported attribute type: ${type}`);
}
return {
content: [
{
type: "text",
text: `${type.charAt(0).toUpperCase() + type.slice(1)} attribute '${key}' created successfully in collection ${collectionId}`
}
]
};
}
private async bulkCreateAttributes(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, attributes } = args;
const results = [];
const errors = [];
for (const attr of attributes) {
try {
await this.createAttribute({
databaseId,
collectionId,
...attr
});
results.push(`${attr.key} (${attr.type})`);
} catch (error) {
errors.push(`${attr.key} (${attr.type}): ${error}`);
}
}
return {
content: [
{
type: "text",
text: `Bulk Attribute Creation Results:
Successful (${results.length}):
${results.join('\n')}
${errors.length > 0 ? `Failed (${errors.length}):
${errors.join('\n')}` : ''}`
}
]
};
}
private async bulkDeleteAttributes(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, attributeKeys } = args;
const results = [];
const errors = [];
for (const key of attributeKeys) {
try {
await this.databases.deleteAttribute(databaseId, collectionId, key);
results.push(`${key}`);
} catch (error) {
errors.push(`${key}: ${error}`);
}
}
return {
content: [
{
type: "text",
text: `Bulk Attribute Deletion Results:
Successful (${results.length}):
${results.join('\n')}
${errors.length > 0 ? `Failed (${errors.length}):
${errors.join('\n')}` : ''}`
}
]
};
}
private async listAttributes(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId } = args;
const attributes = await this.databases.listAttributes(databaseId, collectionId);
const attributeList = attributes.attributes.map((attr: any) =>
`- ${attr.key} (${attr.type}) - Required: ${attr.required}`
).join('\n');
return {
content: [
{
type: "text",
text: `Attributes in collection ${collectionId} (${attributes.total}):\n${attributeList}`
}
]
};
}
private async deleteAttribute(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, key } = args;
await this.databases.deleteAttribute(databaseId, collectionId, key);
return {
content: [
{
type: "text",
text: `Attribute '${key}' deleted successfully from collection ${collectionId}`
}
]
};
}
// Index Operations
private async createIndex(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, key, type, attributes } = args;
const index = await this.databases.createIndex(databaseId, collectionId, key, type, attributes);
return {
content: [
{
type: "text",
text: `Index '${key}' created successfully in collection ${collectionId}`
}
]
};
}
private async listIndexes(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId } = args;
const indexes = await this.databases.listIndexes(databaseId, collectionId);
const indexList = indexes.indexes.map((idx: any) =>
`- ${idx.key} (${idx.type}) - Attributes: ${idx.attributes.join(', ')}`
).join('\n');
return {
content: [
{
type: "text",
text: `Indexes in collection ${collectionId} (${indexes.total}):\n${indexList}`
}
]
};
}
private async deleteIndex(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, key } = args;
await this.databases.deleteIndex(databaseId, collectionId, key);
return {
content: [
{
type: "text",
text: `Index '${key}' deleted successfully from collection ${collectionId}`
}
]
};
}
private async getAttribute(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, key } = args;
const attribute = await this.databases.getAttribute(databaseId, collectionId, key) as any;
return {
content: [
{
type: "text",
text: `Attribute Details:
- Key: ${attribute.key}
- Type: ${attribute.type}
- Required: ${attribute.required}
- Status: ${attribute.status}
${attribute.size ? `- Size: ${attribute.size}` : ''}
${attribute.min !== undefined ? `- Min: ${attribute.min}` : ''}
${attribute.max !== undefined ? `- Max: ${attribute.max}` : ''}
${attribute.elements ? `- Elements: ${attribute.elements.join(', ')}` : ''}
${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
}
]
};
}
private async updateAttribute(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const {
databaseId,
collectionId,
key,
type,
required,
default: defaultValue,
size,
min,
max,
elements
} = args;
let attribute;
switch (type) {
case 'string':
attribute = await this.databases.updateStringAttribute(
databaseId, collectionId, key, required, defaultValue, size
);
break;
case 'integer':
attribute = await this.databases.updateIntegerAttribute(
databaseId, collectionId, key, required, min, max, defaultValue || null
);
break;
case 'float':
attribute = await this.databases.updateFloatAttribute(
databaseId, collectionId, key, required, min, max, defaultValue || null
);
break;
case 'boolean':
attribute = await this.databases.updateBooleanAttribute(
databaseId, collectionId, key, required, defaultValue
);
break;
case 'datetime':
attribute = await this.databases.updateDatetimeAttribute(
databaseId, collectionId, key, required, defaultValue
);
break;
case 'email':
attribute = await this.databases.updateEmailAttribute(
databaseId, collectionId, key, required, defaultValue
);
break;
case 'ip':
attribute = await this.databases.updateIpAttribute(
databaseId, collectionId, key, required, defaultValue
);
break;
case 'url':
attribute = await this.databases.updateUrlAttribute(
databaseId, collectionId, key, required, defaultValue
);
break;
case 'enum':
if (!elements || elements.length === 0) {
throw new Error("Enum attributes require 'elements' array with allowed values");
}
attribute = await this.databases.updateEnumAttribute(
databaseId, collectionId, key, elements, required, defaultValue
);
break;
default:
throw new Error(`Unsupported attribute type for update: ${type}. Note: Relationship attributes cannot be updated.`);
}
return {
content: [
{
type: "text",
text: `${type.charAt(0).toUpperCase() + type.slice(1)} attribute '${key}' updated successfully in collection ${collectionId}`
}
]
};
}
private async getCollection(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId } = args;
const collection = await this.databases.getCollection(databaseId, collectionId) as any;
return {
content: [
{
type: "text",
text: `Collection Details:
- ID: ${collection.$id}
- Name: ${collection.name}
- Database: ${collection.databaseId}
- Created: ${collection.$createdAt}
- Updated: ${collection.$updatedAt}
- Document Security: ${collection.documentSecurity}
- Attributes: ${collection.attributes?.length || 0}
- Indexes: ${collection.indexes?.length || 0}`
}
]
};
}
// Index Operations
private async getIndex(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, key } = args;
const index = await this.databases.getIndex(databaseId, collectionId, key) as any;
return {
content: [
{
type: "text",
text: `Index Details:
- Key: ${index.key}
- Type: ${index.type}
- Status: ${index.status}
- Attributes: ${index.attributes.join(', ')}
- Created: ${index.$createdAt}
- Updated: ${index.$updatedAt}`
}
]
};
}
// Document Operations
private async createDocument(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, documentId, data, permissions } = args;
const docId = documentId || ID.unique();
// Validate document against collection schema
try {
const validation = await this.validateDocumentData(databaseId, collectionId, data);
if (!validation.isValid) {
throw new Error(`Document validation failed: ${validation.errors.join(', ')}`);
}
} catch (validationError) {
// If validation fails, provide helpful error message
throw new Error(`Schema validation error: ${validationError}. Use list_attributes to check collection schema.`);
}
const document = await this.databases.createDocument(
databaseId,
collectionId,
docId,
data,
permissions
);
return {
content: [
{
type: "text",
text: `Document created successfully:\n- ID: ${document.$id}\n- Collection: ${document.$collectionId}\n- Created: ${document.$createdAt}\n- Data: ${JSON.stringify(data, null, 2)}`
}
]
};
}
private async getDocument(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, documentId } = args;
const document = await this.databases.getDocument(databaseId, collectionId, documentId);
return {
content: [
{
type: "text",
text: `Document ${documentId}:\n${JSON.stringify(document, null, 2)}`
}
]
};
}
private async listDocuments(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, queries, limit, offset } = args;
// Build query array with limit and offset
const allQueries = [...(queries || [])];
if (limit !== undefined) {
allQueries.push(Query.limit(limit));
}
if (offset !== undefined) {
allQueries.push(Query.offset(offset));
}
const documents = await this.databases.listDocuments(databaseId, collectionId, allQueries) as any;
const currentLimit = limit || 25;
const currentOffset = offset || 0;
const showing = Math.min(documents.documents.length, currentLimit);
const totalDocs = documents.total;
return {
content: [
{
type: "text",
text: `Documents in collection ${collectionId}:\n- Total: ${totalDocs}\n- Showing: ${showing} (offset: ${currentOffset}, limit: ${currentLimit})\n- Has more: ${currentOffset + showing < totalDocs}\n\n${JSON.stringify(documents.documents, null, 2)}`
}
]
};
}
private async updateDocument(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, documentId, data, permissions } = args;
const document = await this.databases.updateDocument(
databaseId,
collectionId,
documentId,
data,
permissions
);
return {
content: [
{
type: "text",
text: `Document ${documentId} updated successfully:\n${JSON.stringify(document, null, 2)}`
}
]
};
}
private async deleteDocument(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, documentId } = args;
await this.databases.deleteDocument(databaseId, collectionId, documentId);
return {
content: [
{
type: "text",
text: `Document ${documentId} deleted successfully from collection ${collectionId}`
}
]
};
}
// User Management Operations
private async createUser(args: any) {
if (!this.users) throw new Error("Users service not initialized");
const { userId, email, phone, password, name } = args;
const uid = userId || ID.unique();
const user = await this.users.create(uid, email, phone, password, name);
return {
content: [
{
type: "text",
text: `User created successfully:\n- ID: ${user.$id}\n- Email: ${user.email}\n- Name: ${user.name}\n- Created: ${user.$createdAt}`
}
]
};
}
private async listUsers(args: any) {
if (!this.users) throw new Error("Users service not initialized");
const { queries, limit, offset } = args;
// Build query array with limit and offset
const allQueries = [...(queries || [])];
if (limit !== undefined) {
allQueries.push(Query.limit(limit));
}
if (offset !== undefined) {
allQueries.push(Query.offset(offset));
}
const users = await this.users.list(allQueries) as any;
const header = `${'Name'.padEnd(20)} ${'Email'.padEnd(30)} ${'Phone'.padEnd(15)} ${'Email Ver'.padEnd(10)} ${'Phone Ver'.padEnd(10)} ID`;
const separator = '-'.repeat(100);
const userList = users.users.map((user: any) => {
const name = (user.name || 'No name').padEnd(20).substring(0, 20);
const email = (user.email || 'No email').padEnd(30).substring(0, 30);
const phone = (user.phone || 'No phone').padEnd(15).substring(0, 15);
const emailVer = (user.emailVerification ? '✅ Yes' : '❌ No').padEnd(10);
const phoneVer = (user.phoneVerification ? '✅ Yes' : '❌ No').padEnd(10);
return `${name} ${email} ${phone} ${emailVer} ${phoneVer} ${user.$id}`;
}).join('\n');
const currentLimit = limit || 25;
const currentOffset = offset || 0;
const showing = users.users.length;
const totalUsers = users.total;
return {
content: [
{
type: "text",
text: `Users:\n- Total: ${totalUsers}\n- Showing: ${showing} (offset: ${currentOffset}, limit: ${currentLimit})\n- Has more: ${currentOffset + showing < totalUsers}\n\n${header}\n${separator}\n${userList}`
}
]
};
}
private async getUser(args: any) {
if (!this.users) throw new Error("Users service not initialized");
const { userId } = args;
const user = await this.users.get(userId);
return {
content: [
{
type: "text",
text: `User ${userId}:\n${JSON.stringify(user, null, 2)}`
}
]
};
}
private async updateUser(args: any) {
if (!this.users) throw new Error("Users service not initialized");
const { userId, email, name, password } = args;
if (!email && !name && !password) {
throw new Error("At least one field (email, name, or password) must be provided");
}
const updates: string[] = [];
if (email) {
await this.users.updateEmail(userId, email);
updates.push(`email updated to: ${email}`);
}
if (name) {
await this.users.updateName(userId, name);
updates.push(`name updated to: ${name}`);
}
if (password) {
await this.users.updatePassword(userId, password);
updates.push(`password updated successfully`);
}
return {
content: [
{
type: "text",
text: `User ${userId} - ${updates.join(', ')}`
}
]
};
}
private async deleteUser(args: any) {
if (!this.users) throw new Error("Users service not initialized");
const { userId } = args;
await this.users.delete(userId);
return {
content: [
{
type: "text",
text: `User ${userId} deleted successfully`
}
]
};
}
private async updateUserPreferences(args: any) {
if (!this.users) throw new Error("Users service not initialized");
const { userId, prefs } = args;
const result = await this.users.updatePrefs(userId, prefs) as any;
return {
content: [
{
type: "text",
text: `User ${userId} preferences updated successfully`
}
]
};
}
// Storage Operations
private async createBucket(args: any) {
if (!this.storage) throw new Error("Storage service not initialized");
const { bucketId, name, permissions, fileSecurity, enabled } = args;
const bid = bucketId || ID.unique();
const bucket = await this.storage.createBucket(bid, name, permissions, fileSecurity, enabled);
return {
content: [
{
type: "text",
text: `Bucket created successfully:\n- ID: ${bucket.$id}\n- Name: ${bucket.name}\n- Created: ${bucket.$createdAt}`
}
]
};
}
private async listBuckets() {
if (!this.storage) throw new Error("Storage service not initialized");
const buckets = await this.storage.listBuckets();
const bucketList = buckets.buckets.map((bucket: any) =>
`- ${bucket.name} (ID: ${bucket.$id})`
).join('\n');
return {
content: [
{
type: "text",
text: `Storage buckets (${buckets.total}):\n${bucketList}`
}
]
};
}
private async getBucket(args: any) {
if (!this.storage) throw new Error("Storage service not initialized");
const { bucketId } = args;
const bucket = await this.storage.getBucket(bucketId);
return {
content: [
{
type: "text",
text: `Bucket ${bucketId}:\n${JSON.stringify(bucket, null, 2)}`
}
]
};
}
private async updateBucket(args: any) {
if (!this.storage) throw new Error("Storage service not initialized");
const { bucketId, name, permissions, fileSecurity, enabled } = args;
const bucket = await this.storage.updateBucket(bucketId, name, permissions, fileSecurity, enabled);
return {
content: [
{
type: "text",
text: `Bucket ${bucketId} updated successfully`
}
]
};
}
private async deleteBucket(args: any) {
if (!this.storage) throw new Error("Storage service not initialized");
const { bucketId } = args;
await this.storage.deleteBucket(bucketId);
return {
content: [
{
type: "text",
text: `Bucket ${bucketId} deleted successfully`
}
]
};
}
private async listFiles(args: any) {
if (!this.storage) throw new Error("Storage service not initialized");
const { bucketId } = args;
const files = await this.storage.listFiles(bucketId);
const fileList = files.files.map((file: any) =>
`- ${file.name} (${file.sizeOriginal} bytes) - ID: ${file.$id}`
).join('\n');
return {
content: [
{
type: "text",
text: `Files in bucket ${bucketId} (${files.total}):\n${fileList}`
}
]
};
}
private async getFile(args: any) {
if (!this.storage) throw new Error("Storage service not initialized");
const { bucketId, fileId } = args;
const file = await this.storage.getFile(bucketId, fileId) as any;
return {
content: [
{
type: "text",
text: `File ${fileId}:\n${JSON.stringify(file, null, 2)}`
}
]
};
}
private async getFileUrl(args: any) {
if (!this.storage) throw new Error("Storage service not initialized");
const { bucketId, fileId, type, width, height, gravity, quality, borderWidth, borderColor, borderRadius, opacity, rotation, background, output } = args;
let url: string;
let urlType: string;
switch (type) {
case 'download':
url = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/download?project=${this.config?.projectId}`;
urlType = 'Download';
break;
case 'view':
url = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/view?project=${this.config?.projectId}`;
urlType = 'View';
break;
case 'preview':
// Build query parameters for preview
const params = new URLSearchParams();
if (width !== undefined) params.append('width', width.toString());
if (height !== undefined) params.append('height', height.toString());
if (gravity) params.append('gravity', gravity);
if (quality !== undefined) params.append('quality', quality.toString());
if (borderWidth !== undefined) params.append('borderWidth', borderWidth.toString());
if (borderColor) params.append('borderColor', borderColor);
if (borderRadius !== undefined) params.append('borderRadius', borderRadius.toString());
if (opacity !== undefined) params.append('opacity', opacity.toString());
if (rotation !== undefined) params.append('rotation', rotation.toString());
if (background) params.append('background', background);
if (output) params.append('output', output);
params.append('project', this.config?.projectId || '');
url = `${this.config?.apiEndpoint}/storage/buckets/${bucketId}/files/${fileId}/preview?${params.toString()}`;
urlType = 'Preview';
break;
default:
throw new Error(`Invalid URL type: ${type}. Must be 'download', 'preview', or 'view'`);
}
return {
content: [
{
type: "text",
text: `${urlType} URL for file ${fileId}:\n${url}`
}
]
};
}
private async deleteFile(args: any) {
if (!this.storage) throw new Error("Storage service not initialized");
const { bucketId, fileId } = args;
await this.storage.deleteFile(bucketId, fileId);
return {
content: [
{
type: "text",
text: `File ${fileId} deleted successfully from bucket ${bucketId}`
}
]
};
}
private async createFile(args: any) {
if (!this.storage) throw new Error("Storage service not initialized");
const { bucketId, fileId, filePath, permissions } = args;
const fid = fileId || ID.unique();
try {
// Check if file exists
if (!existsSync(filePath)) {
throw new Error(`File not found: ${filePath}`);
}
// Create InputFile from path and upload
const fileName = filePath.split(/[\\/]/).pop() || 'file';
const file = InputFile.fromPath(filePath, fileName);
const result = await this.storage.createFile(bucketId, fid, file, permissions) as any;
return {
content: [
{
type: "text",
text: `File uploaded successfully:\n- File ID: ${result.$id}\n- Name: ${result.name}\n- Size: ${result.sizeOriginal} bytes\n- MIME Type: ${result.mimeType}`
}
]
};
} catch (error: any) {
return {
content: [
{
type: "text",
text: `Error uploading file: ${error.message}`
}
]
};
}
}
private async updateFile(args: any) {
if (!this.storage) throw new Error("Storage service not initialized");
const { bucketId, fileId, name, permissions } = args;
const file = await this.storage.updateFile(bucketId, fileId, name, permissions) as any;
return {
content: [
{
type: "text",
text: `File ${fileId} metadata updated successfully`
}
]
};
}
// Function Operations
private async createFunction(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId, name, runtime, execute, events, schedule, timeout } = args;
const fid = functionId || ID.unique();
const func = await this.functions.create(fid, name, runtime, execute, events, schedule, timeout);
return {
content: [
{
type: "text",
text: `Function created successfully:\n- ID: ${func.$id}\n- Name: ${func.name}\n- Runtime: ${func.runtime}\n- Created: ${func.$createdAt}`
}
]
};
}
private async listFunctions() {
if (!this.functions) throw new Error("Functions service not initialized");
const functions = await this.functions.list();
const functionList = functions.functions.map((func: any) =>
`- ${func.name} (${func.runtime}) - ID: ${func.$id}`
).join('\n');
return {
content: [
{
type: "text",
text: `Functions (${functions.total}):\n${functionList}`
}
]
};
}
private async getFunction(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId } = args;
const func = await this.functions.get(functionId);
return {
content: [
{
type: "text",
text: `Function ${functionId}:\n${JSON.stringify(func, null, 2)}`
}
]
};
}
private async updateFunction(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId, name, runtime, execute, events, schedule, timeout } = args;
const func = await this.functions.update(functionId, name, runtime, execute, events, schedule, timeout);
return {
content: [
{
type: "text",
text: `Function ${functionId} updated successfully`
}
]
};
}
private async deleteFunction(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId } = args;
await this.functions.delete(functionId);
return {
content: [
{
type: "text",
text: `Function ${functionId} deleted successfully`
}
]
};
}
private async listFunctionDeployments(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId, queries, search } = args;
const deployments = await this.functions.listDeployments(functionId, queries, search) as any;
const deploymentList = deployments.deployments.map((deployment: any) =>
`- ${deployment.status} - ID: ${deployment.$id} - Created: ${deployment.$createdAt}`
).join('\n');
return {
content: [
{
type: "text",
text: `Function ${functionId} deployments (${deployments.total}):\n${deploymentList}`
}
]
};
}
private async deleteFunctionDeployment(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId, deploymentId } = args;
await this.functions.deleteDeployment(functionId, deploymentId);
return {
content: [
{
type: "text",
text: `Function ${functionId} deployment ${deploymentId} deleted successfully`
}
]
};
}
private async createFunctionVariable(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId, key, value, secret } = args;
const variable = await this.functions.createVariable(functionId, key, value) as any;
return {
content: [
{
type: "text",
text: `Variable created for function ${functionId}:\n- Key: ${variable.key}\n- ID: ${variable.$id}\n- Secret: ${variable.secret ? 'Yes' : 'No'}`
}
]
};
}
private async listFunctionVariables(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId } = args;
const variables = await this.functions.listVariables(functionId) as any;
const variableList = variables.variables.map((variable: any) =>
`- ${variable.key} (${variable.secret ? 'Secret' : 'Public'}) - ID: ${variable.$id}`
).join('\n');
return {
content: [
{
type: "text",
text: `Function ${functionId} variables (${variables.total}):\n${variableList}`
}
]
};
}
private async getFunctionVariable(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId, variableId } = args;
const variable = await this.functions.getVariable(functionId, variableId) as any;
return {
content: [
{
type: "text",
text: `Function ${functionId} variable ${variableId}:\n${JSON.stringify(variable, null, 2)}`
}
]
};
}
private async updateFunctionVariable(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId, variableId, key, value, secret } = args;
const variable = await this.functions.updateVariable(functionId, variableId, key, value) as any;
return {
content: [
{
type: "text",
text: `Function ${functionId} variable ${variableId} updated successfully`
}
]
};
}
private async deleteFunctionVariable(args: any) {
if (!this.functions) throw new Error("Functions service not initialized");
const { functionId, variableId } = args;
await this.functions.deleteVariable(functionId, variableId);
return {
content: [
{
type: "text",
text: `Function ${functionId} variable ${variableId} deleted successfully`
}
]
};
}
// Team Operations
private async createTeam(args: any) {
if (!this.teams) throw new Error("Teams service not initialized");
const { teamId, name, roles } = args;
const tid = teamId || ID.unique();
const team = await this.teams.create(tid, name, roles);
return {
content: [
{
type: "text",
text: `Team created successfully:\n- ID: ${team.$id}\n- Name: ${team.name}\n- Created: ${team.$createdAt}`
}
]
};
}
private async listTeams() {
if (!this.teams) throw new Error("Teams service not initialized");
const teams = await this.teams.list();
const teamList = teams.teams.map((team: any) =>
`- ${team.name} - ID: ${team.$id}`
).join('\n');
return {
content: [
{
type: "text",
text: `Teams (${teams.total}):\n${teamList}`
}
]
};
}
private async getTeam(args: any) {
if (!this.teams) throw new Error("Teams service not initialized");
const { teamId } = args;
const team = await this.teams.get(teamId);
return {
content: [
{
type: "text",
text: `Team ${teamId}:\n${JSON.stringify(team, null, 2)}`
}
]
};
}
private async updateTeam(args: any) {
if (!this.teams) throw new Error("Teams service not initialized");
const { teamId, name } = args;
const team = await this.teams.updateName(teamId, name);
return {
content: [
{
type: "text",
text: `Team ${teamId} name updated to: ${name}`
}
]
};
}
private async deleteTeam(args: any) {
if (!this.teams) throw new Error("Teams service not initialized");
const { teamId } = args;
await this.teams.delete(teamId);
return {
content: [
{
type: "text",
text: `Team ${teamId} deleted successfully`
}
]
};
}
// Messaging Operations
private async createMessagingProvider(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { providerId, name, type, enabled } = args;
const pid = providerId || ID.unique();
try {
let result;
// Create provider based on type
switch (type) {
case 'email':
// Create SMTP provider (most common email provider)
result = await this.messaging.createSmtpProvider(
pid,
name,
'smtp.gmail.com', // default host
587, // default port
'', // username (to be configured later)
'', // password (to be configured later)
'tls' as any, // encryption
false, // autoTLS
'' as any, // mailer
enabled || true
) as any;
break;
case 'sms':
// Create Twilio provider (most common SMS provider)
result = await this.messaging.createTwilioProvider(
pid,
name,
'', // Account SID (to be configured later)
'', // Auth token (to be configured later)
'', // From number (to be configured later)
enabled || true
) as any;
break;
case 'push':
// Create FCM provider (most common push provider)
result = await this.messaging.createFcmProvider(
pid,
name,
{}, // Service account JSON (to be configured later)
enabled || true
) as any;
break;
default:
throw new Error(`Unsupported provider type: ${type}. Use 'email', 'sms', or 'push'`);
}
return {
content: [
{
type: "text",
text: `${type.toUpperCase()} provider created:\n- ID: ${result.$id}\n- Name: ${result.name}\n- Type: ${result.type}\n- Enabled: ${result.enabled}\n- Note: Configure credentials in Appwrite Console`
}
]
};
} catch (error: any) {
return {
content: [
{
type: "text",
text: `Error creating ${type} provider: ${error.message}`
}
]
};
}
}
private async listMessagingProviders() {
if (!this.messaging) throw new Error("Messaging service not initialized");
try {
const providers = await this.messaging.listProviders() as any;
if (providers.providers.length === 0) {
return {
content: [
{
type: "text",
text: "No messaging providers configured"
}
]
};
}
const providerList = providers.providers.map((provider: any) =>
`- ${provider.name} (${provider.type}) - ID: ${provider.$id} - Enabled: ${provider.enabled}`
).join('\n');
return {
content: [
{
type: "text",
text: `Messaging providers (${providers.providers.length}):\n${providerList}`
}
]
};
} catch (error: any) {
return {
content: [
{
type: "text",
text: `Error listing providers: ${error.message}`
}
]
};
}
}
private async getMessagingProvider(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { providerId } = args;
const provider = await this.messaging.getProvider(providerId) as any;
return {
content: [
{
type: "text",
text: `Messaging provider ${providerId}:\n${JSON.stringify(provider, null, 2)}`
}
]
};
}
private async updateMessagingProvider(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { providerId, name, enabled } = args;
try {
// First get the provider to determine its type
const provider = await this.messaging.getProvider(providerId) as any;
let result;
switch (provider.type) {
case 'smtp':
result = await this.messaging.updateSmtpProvider(
providerId,
name || provider.name,
provider.host,
provider.port,
provider.username,
provider.password,
provider.encryption,
provider.autoTLS,
provider.mailer,
enabled !== undefined ? enabled : provider.enabled
) as any;
break;
case 'twilio':
result = await this.messaging.updateTwilioProvider(
providerId,
name || provider.name,
provider.accountSid,
provider.authToken,
provider.from,
enabled !== undefined ? enabled : provider.enabled
) as any;
break;
case 'fcm':
result = await this.messaging.updateFcmProvider(
providerId,
name || provider.name,
provider.serviceAccountJSON,
enabled !== undefined ? enabled : provider.enabled
) as any;
break;
default:
throw new Error(`Unsupported provider type: ${provider.type}`);
}
return {
content: [
{
type: "text",
text: `${provider.type.toUpperCase()} provider updated:\n- ID: ${result.$id}\n- Name: ${result.name}\n- Enabled: ${result.enabled}`
}
]
};
} catch (error: any) {
return {
content: [
{
type: "text",
text: `Error updating provider: ${error.message}`
}
]
};
}
}
private async deleteMessagingProvider(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { providerId } = args;
await this.messaging.deleteProvider(providerId);
return {
content: [
{
type: "text",
text: `Messaging provider ${providerId} deleted successfully`
}
]
};
}
private async createMessagingMessage(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { messageId, subject, content, topics, users, targets } = args;
const mid = messageId || ID.unique();
try {
// Check if any email providers are available
const providers = await this.messaging.listProviders() as any;
const emailProviders = providers.providers.filter((p: any) =>
p.type === 'smtp' || p.type === 'mailgun' || p.type === 'sendgrid'
);
if (emailProviders.length === 0) {
throw new Error("No email providers configured. Please create an email provider first using create_messaging_provider.");
}
// Create email message (assuming email provider since most common)
const result = await this.messaging.createEmail(
mid,
subject || "No Subject",
content,
topics,
users,
targets
) as any;
return {
content: [
{
type: "text",
text: `Email message created and queued:\n- ID: ${result.$id}\n- Subject: ${result.subject}\n- Status: ${result.status}\n- Scheduled: ${result.scheduledAt || 'Immediate'}`
}
]
};
} catch (error: any) {
return {
content: [
{
type: "text",
text: `Error creating message: ${error.message}`
}
]
};
}
}
private async listMessagingMessages(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { queries } = args;
const messages = await this.messaging.listMessages(queries) as any;
const messageList = messages.messages.map((message: any) =>
`- ${message.subject || 'No subject'} - Status: ${message.status} - ID: ${message.$id}`
).join('\n');
return {
content: [
{
type: "text",
text: `Messages (${messages.total}):\n${messageList}`
}
]
};
}
private async getMessagingMessage(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { messageId } = args;
const message = await this.messaging.getMessage(messageId) as any;
return {
content: [
{
type: "text",
text: `Message ${messageId}:\n${JSON.stringify(message, null, 2)}`
}
]
};
}
private async updateMessagingMessage(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { messageId, topics, users, targets, status } = args;
// Note: Update message functionality depends on message type
return {
content: [
{
type: "text",
text: `Message ${messageId} update requested - use specific message update methods`
}
]
};
}
private async deleteMessagingMessage(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { messageId } = args;
// Note: Use specific delete methods for different message types
// await this.messaging.deleteMessage(messageId);
return {
content: [
{
type: "text",
text: `Message ${messageId} deleted successfully`
}
]
};
}
private async createMessagingTopic(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { topicId, name, description } = args;
const tid = topicId || ID.unique();
const topic = await this.messaging.createTopic(tid, name, description) as any;
return {
content: [
{
type: "text",
text: `Topic created:\n- ID: ${topic.$id}\n- Name: ${topic.name}\n- Description: ${topic.description || 'No description'}`
}
]
};
}
private async listMessagingTopics(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { queries } = args;
const topics = await this.messaging.listTopics(queries) as any;
const topicList = topics.topics.map((topic: any) =>
`- ${topic.name} - ID: ${topic.$id}`
).join('\n');
return {
content: [
{
type: "text",
text: `Topics (${topics.total}):\n${topicList}`
}
]
};
}
private async getMessagingTopic(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { topicId } = args;
const topic = await this.messaging.getTopic(topicId) as any;
return {
content: [
{
type: "text",
text: `Topic ${topicId}:\n${JSON.stringify(topic, null, 2)}`
}
]
};
}
private async updateMessagingTopic(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { topicId, name, description } = args;
const topic = await this.messaging.updateTopic(topicId, name, description) as any;
return {
content: [
{
type: "text",
text: `Topic ${topicId} updated successfully`
}
]
};
}
private async deleteMessagingTopic(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { topicId } = args;
await this.messaging.deleteTopic(topicId);
return {
content: [
{
type: "text",
text: `Topic ${topicId} deleted successfully`
}
]
};
}
private async createMessagingSubscriber(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { topicId, subscriberId, targetId } = args;
const sid = subscriberId || ID.unique();
const subscriber = await this.messaging.createSubscriber(topicId, sid, targetId) as any;
return {
content: [
{
type: "text",
text: `Subscriber created:\n- ID: ${subscriber.$id}\n- Topic: ${topicId}\n- Target: ${targetId}`
}
]
};
}
private async listMessagingSubscribers(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { topicId, queries } = args;
const subscribers = await this.messaging.listSubscribers(topicId, queries) as any;
const subscriberList = subscribers.subscribers.map((subscriber: any) =>
`- Target: ${subscriber.targetId} - ID: ${subscriber.$id}`
).join('\n');
return {
content: [
{
type: "text",
text: `Subscribers for topic ${topicId} (${subscribers.total}):\n${subscriberList}`
}
]
};
}
private async getMessagingSubscriber(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { topicId, subscriberId } = args;
const subscriber = await this.messaging.getSubscriber(topicId, subscriberId) as any;
return {
content: [
{
type: "text",
text: `Subscriber ${subscriberId}:\n${JSON.stringify(subscriber, null, 2)}`
}
]
};
}
private async deleteMessagingSubscriber(args: any) {
if (!this.messaging) throw new Error("Messaging service not initialized");
const { topicId, subscriberId } = args;
await this.messaging.deleteSubscriber(topicId, subscriberId);
return {
content: [
{
type: "text",
text: `Subscriber ${subscriberId} deleted from topic ${topicId}`
}
]
};
}
// Smart Schema Operations
private async autoDetectSchema(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionName, sampleData, collectionId } = args;
const cid = collectionId || ID.unique();
// Analyze sample data to determine schema
const schema = this.analyzeDataStructure(sampleData);
// Create collection
const collection = await this.databases.createCollection(databaseId, cid, collectionName);
// Create attributes based on detected schema
const createdAttributes = [];
for (const attr of schema.attributes) {
try {
switch (attr.type) {
case 'string':
await this.databases.createStringAttribute(
databaseId, cid, attr.key, attr.size || 255, attr.required || false
);
break;
case 'integer':
await this.databases.createIntegerAttribute(
databaseId, cid, attr.key, attr.required || false
);
break;
case 'boolean':
await this.databases.createBooleanAttribute(
databaseId, cid, attr.key, attr.required || false
);
break;
case 'email':
await this.databases.createEmailAttribute(
databaseId, cid, attr.key, attr.required || false
);
break;
case 'datetime':
await this.databases.createDatetimeAttribute(
databaseId, cid, attr.key, attr.required || false
);
break;
}
createdAttributes.push(attr);
} catch (error) {
console.error(`Failed to create attribute ${attr.key}:`, error);
}
}
// Suggest indexes for commonly queried fields
const suggestedIndexes = schema.suggestedIndexes;
return {
content: [
{
type: "text",
text: `Schema auto-detected and collection created successfully:
- Collection: ${collectionName} (ID: ${cid})
- Attributes created: ${createdAttributes.length}
- Detected types: ${createdAttributes.map(a => `${a.key} (${a.type})`).join(', ')}
- Suggested indexes: ${suggestedIndexes.join(', ')}
Schema Analysis:
${JSON.stringify(schema, null, 2)}`
}
]
};
}
private analyzeDataStructure(sampleData: any[]): any {
const fieldTypes: { [key: string]: { type: string; required: boolean; size?: number; samples: any[] } } = {};
const totalRecords = sampleData.length;
// Analyze each sample document
sampleData.forEach(doc => {
Object.keys(doc).forEach(key => {
if (!fieldTypes[key]) {
fieldTypes[key] = { type: 'unknown', required: false, samples: [] };
}
fieldTypes[key].samples.push(doc[key]);
});
});
// Determine types and properties
const attributes = Object.keys(fieldTypes).map(key => {
const field = fieldTypes[key];
const samples = field.samples;
const nonNullSamples = samples.filter(s => s !== null && s !== undefined);
// Filter out empty/meaningless values for better required field detection
const meaningfulSamples = samples.filter(s =>
s !== null &&
s !== undefined &&
s !== "" &&
(typeof s === 'string' ? s.trim() !== "" : true)
);
// Calculate required percentage based on meaningful data
const requiredPercentage = meaningfulSamples.length / totalRecords;
const required = requiredPercentage > 0.7; // 70% threshold for required (more lenient)
// Detect type
let type = 'string';
let size = 255;
if (meaningfulSamples.length > 0) {
const firstSample = meaningfulSamples[0];
if (typeof firstSample === 'boolean') {
type = 'boolean';
} else if (typeof firstSample === 'number' && Number.isInteger(firstSample)) {
type = 'integer';
} else if (typeof firstSample === 'string') {
// Check for email pattern
if (meaningfulSamples.some(s => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s))) {
type = 'email';
}
// Check for datetime pattern
else if (meaningfulSamples.some(s => !isNaN(Date.parse(s)))) {
type = 'datetime';
} else {
type = 'string';
// Calculate max string length from meaningful samples
const maxLength = Math.max(...meaningfulSamples.map(s => String(s).length));
size = Math.max(255, Math.ceil(maxLength * 1.2)); // 20% buffer
}
}
}
return { key, type, required, size: type === 'string' ? size : undefined };
});
// Suggest indexes for common query fields
const suggestedIndexes = attributes
.filter(attr => ['email', 'string'].includes(attr.type) && attr.key.match(/(id|email|name|slug|status|type)/i))
.map(attr => attr.key);
return { attributes, suggestedIndexes, totalRecords, analysis: fieldTypes };
}
private async suggestIndexes(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, queryPatterns } = args;
// Get collection attributes
const attributes = await this.databases.listAttributes(databaseId, collectionId);
const attributeMap = new Map(attributes.attributes.map((attr: any) => [attr.key, attr]));
// Get existing indexes
const existingIndexes = await this.databases.listIndexes(databaseId, collectionId);
const existingIndexKeys = new Set(existingIndexes.indexes.map((idx: any) => idx.key));
const suggestions: any[] = [];
// Standard suggestions based on attribute types
attributes.attributes.forEach((attr: any) => {
const indexKey = `idx_${attr.key}`;
if (existingIndexKeys.has(indexKey)) return;
if (attr.type === 'email' || attr.key.includes('email')) {
suggestions.push({
key: indexKey,
type: 'unique',
attributes: [attr.key],
reason: 'Email fields should have unique indexes for fast lookups and uniqueness constraints'
});
} else if (attr.key.match(/(id|name|slug|status|type|category)/i)) {
suggestions.push({
key: indexKey,
type: 'key',
attributes: [attr.key],
reason: `Common query field '${attr.key}' would benefit from indexing`
});
} else if (attr.type === 'string' && attr.key.match(/(title|description|content|body)/i)) {
suggestions.push({
key: `fulltext_${attr.key}`,
type: 'fulltext',
attributes: [attr.key],
reason: `Text field '${attr.key}' would benefit from full-text search indexing`
});
}
});
// Analyze query patterns if provided
if (queryPatterns && queryPatterns.length > 0) {
queryPatterns.forEach((pattern: string, index: number) => {
const fields = this.extractFieldsFromQuery(pattern);
if (fields.length > 1) {
const compositeKey = `composite_${fields.join('_')}`;
if (!existingIndexKeys.has(compositeKey)) {
suggestions.push({
key: compositeKey,
type: 'key',
attributes: fields,
reason: `Composite index for query pattern: "${pattern}"`
});
}
}
});
}
// Remove duplicates and limit suggestions
const uniqueSuggestions = suggestions.slice(0, 10);
return {
content: [
{
type: "text",
text: `Index Recommendations for Collection ${collectionId}:
Found ${attributes.total} attributes, ${existingIndexes.total} existing indexes
Recommended Indexes (${uniqueSuggestions.length}):
${uniqueSuggestions.map(s =>
`${s.key} (${s.type}) on [${s.attributes.join(', ')}]
Reason: ${s.reason}`
).join('\n\n')}
Existing Indexes:
${existingIndexes.indexes.map((idx: any) => `${idx.key} (${idx.type}) on [${idx.attributes.join(', ')}]`).join('\n')}
To create recommended indexes, use the create_index tool with the suggested parameters.`
}
]
};
}
private extractFieldsFromQuery(query: string): string[] {
// Simple pattern extraction - look for field names in common query patterns
const patterns = [
/Query\.equal\(['"]([^'"]+)['"]/g,
/Query\.search\(['"]([^'"]+)['"]/g,
/Query\.orderBy\(['"]([^'"]+)['"]/g,
/where\s+(\w+)\s*=/gi,
/order\s+by\s+(\w+)/gi,
/group\s+by\s+(\w+)/gi
];
const fields = new Set<string>();
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<any> {
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<void> {
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);