diff --git a/src/index.ts b/src/index.ts index abefd1d..0509ca2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -401,6 +401,7 @@ class AppwriteMCPServer { 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)" } }, @@ -891,6 +892,7 @@ class AppwriteMCPServer { 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)" } }, @@ -1533,6 +1535,17 @@ class AppwriteMCPServer { 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, @@ -1629,10 +1642,10 @@ class AppwriteMCPServer { private async createUser(args: any) { if (!this.users) throw new Error("Users service not initialized"); - const { userId, email, password, name } = args; + const { userId, email, phone, password, name } = args; const uid = userId || ID.unique(); - const user = await this.users.create(uid, email, password, name); + const user = await this.users.create(uid, email, phone, password, name); return { content: [ @@ -2371,6 +2384,71 @@ ${attributes.attributes.map((attr: any) => }; } + // 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"); @@ -3608,9 +3686,9 @@ For accurate usage tracking, consider: for (const user of users) { try { - const { userId, email, password, name } = user; + const { userId, email, phone, password, name } = user; const uid = userId || ID.unique(); - const createdUser = await this.users.create(uid, email, password, name); + const createdUser = await this.users.create(uid, email, phone, password, name); results.push(`✅ ${createdUser.email} (${createdUser.$id})`); } catch (error) { errors.push(`❌ ${user.email}: ${error}`);