Compare commits

...

2 Commits

View File

@@ -735,8 +735,22 @@ class AppwriteMCPServer {
case "get":
if (!key) throw new Error("key is required for get action");
const attribute = await this.databases.getAttribute(databaseId, collectionId, key);
const attr = attribute as any;
// Build detailed attribute information
let details = `Attribute Details:\n- Key: ${attr.key}\n- Type: ${attr.type}\n- Required: ${attr.required}\n- Status: ${attr.status}`;
// Add type-specific details
if (attr.size !== undefined) details += `\n- Size: ${attr.size}`;
if (attr.min !== undefined) details += `\n- Min: ${attr.min}`;
if (attr.max !== undefined) details += `\n- Max: ${attr.max}`;
if (attr.default !== undefined && attr.default !== null) details += `\n- Default: ${attr.default}`;
if (attr.array !== undefined) details += `\n- Array: ${attr.array}`;
if (attr.elements !== undefined) details += `\n- Elements: ${attr.elements.join(', ')}`;
if (attr.format !== undefined) details += `\n- Format: ${attr.format}`;
return {
content: [{ type: "text", text: `Attribute Details:\n- Key: ${(attribute as any).key}\n- Type: ${(attribute as any).type}\n- Required: ${(attribute as any).required}\n- Status: ${(attribute as any).status}` }]
content: [{ type: "text", text: details }]
};
case "list":
@@ -753,7 +767,16 @@ class AppwriteMCPServer {
let updateResult;
switch (type) {
case "string":
updateResult = await this.databases.updateStringAttribute(databaseId, collectionId, key, required, otherArgs.default);
// Appwrite API requires default param, use null for required attrs if not provided
const strDefault = otherArgs.default !== undefined ? otherArgs.default : null;
updateResult = await this.databases.updateStringAttribute(
databaseId,
collectionId,
key,
required,
strDefault as any,
otherArgs.size
);
break;
case "integer":
updateResult = await this.databases.updateIntegerAttribute(databaseId, collectionId, key, required, otherArgs.min, otherArgs.max, otherArgs.default);
@@ -971,12 +994,127 @@ class AppwriteMCPServer {
}
}
private parseQuery(queryString: string): string {
// Parse query strings like 'equal("field", "value")' into proper Query objects
// This regex matches: functionName("field", value) or functionName("field", [values])
const match = queryString.match(/^(\w+)\((.*)\)$/);
if (!match) {
throw new Error(`Invalid query format: ${queryString}. Expected format: equal("field", "value")`);
}
const [, method, argsStr] = match;
// Parse arguments - handle quoted strings and arrays
const args: any[] = [];
let current = '';
let inQuotes = false;
let inArray = false;
for (let i = 0; i < argsStr.length; i++) {
const char = argsStr[i];
if (char === '"' && argsStr[i - 1] !== '\\') {
inQuotes = !inQuotes;
continue;
}
if (char === '[' && !inQuotes) {
inArray = true;
current += char;
continue;
}
if (char === ']' && !inQuotes) {
inArray = false;
current += char;
continue;
}
if (char === ',' && !inQuotes && !inArray) {
args.push(current.trim());
current = '';
continue;
}
current += char;
}
if (current.trim()) {
args.push(current.trim());
}
// Convert parsed arguments to proper types
const processedArgs = args.map(arg => {
// Handle arrays
if (arg.startsWith('[') && arg.endsWith(']')) {
const items = arg.slice(1, -1).split(',').map((item: string) => item.trim().replace(/^"(.*)"$/, '$1'));
return items;
}
// Handle numbers
if (/^-?\d+(\.\d+)?$/.test(arg)) {
return Number(arg);
}
// Handle booleans
if (arg === 'true') return true;
if (arg === 'false') return false;
// Handle strings (remove quotes)
return arg.replace(/^"(.*)"$/, '$1');
});
// Map to Query methods
const queryMap: Record<string, (...args: any[]) => string> = {
equal: Query.equal,
notEqual: Query.notEqual,
lessThan: Query.lessThan,
lessThanEqual: Query.lessThanEqual,
greaterThan: Query.greaterThan,
greaterThanEqual: Query.greaterThanEqual,
search: Query.search,
isNull: Query.isNull,
isNotNull: Query.isNotNull,
between: Query.between,
startsWith: Query.startsWith,
endsWith: Query.endsWith,
select: Query.select,
orderDesc: Query.orderDesc,
orderAsc: Query.orderAsc,
limit: Query.limit,
offset: Query.offset,
contains: Query.contains,
or: Query.or,
and: Query.and,
};
if (!queryMap[method]) {
throw new Error(`Unknown query method: ${method}. Available methods: ${Object.keys(queryMap).join(', ')}`);
}
return queryMap[method](...processedArgs);
}
private async listDocuments(args: any) {
if (!this.databases) throw new Error("Databases not initialized");
const { databaseId, collectionId, queries, limit, offset } = args;
const documents = await this.databases.listDocuments(databaseId, collectionId, queries || []);
// Parse query strings into Query objects
const parsedQueries: string[] = [];
if (queries && Array.isArray(queries)) {
for (const queryStr of queries) {
try {
parsedQueries.push(this.parseQuery(queryStr));
} catch (error) {
throw new Error(`Failed to parse query "${queryStr}": ${error instanceof Error ? error.message : String(error)}`);
}
}
}
// Add limit and offset if provided
if (limit) parsedQueries.push(Query.limit(limit));
if (offset) parsedQueries.push(Query.offset(offset));
const documents = await this.databases.listDocuments(databaseId, collectionId, parsedQueries);
const docList = documents.documents.map(doc => `- ${doc.$id} (updated: ${doc.$updatedAt})`).join('\n');
return {
content: [{ type: "text", text: `Documents in collection ${collectionId} (${documents.total}):\n${docList}` }]