5250 lines
172 KiB
JavaScript
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); |