fixed 3 tools

This commit is contained in:
2025-07-27 23:31:51 +03:00
parent 1ebc34764b
commit 0a362b37b1
2 changed files with 332 additions and 86 deletions

View File

@@ -26,6 +26,7 @@ import {
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";
@@ -666,12 +667,12 @@ class AppwriteMCPServer {
},
{
name: "update_user_labels",
description: "Update user labels",
description: "Update user labels (alphanumeric only: A-Z, a-z, 0-9)",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "User ID" },
labels: { type: "array", description: "User labels", items: { type: "string" } }
labels: { type: "array", description: "User labels (max 1000, 1-36 chars each, alphanumeric only)", items: { type: "string" } }
},
required: ["userId", "labels"]
}
@@ -1664,7 +1665,7 @@ class AppwriteMCPServer {
},
{
name: "delete_session",
description: "Delete a user session",
description: "Delete a user session (requires 'account' scope in API key)",
inputSchema: {
type: "object",
properties: {
@@ -2639,12 +2640,12 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
break;
case 'integer':
attribute = await this.databases.updateIntegerAttribute(
databaseId, collectionId, key, required, min, max, defaultValue
databaseId, collectionId, key, required, min, max, defaultValue || null
);
break;
case 'float':
attribute = await this.databases.updateFloatAttribute(
databaseId, collectionId, key, required, min, max, defaultValue
databaseId, collectionId, key, required, min, max, defaultValue || null
);
break;
case 'boolean':
@@ -3127,13 +3128,26 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
const { userId, labels } = args;
const user = await this.users.updateLabels(userId, labels) as any;
// Validate labels: alphanumeric only, 1-36 chars each
const validLabels = labels.filter((label: string) => {
const isValid = /^[A-Za-z0-9]{1,36}$/.test(label);
if (!isValid) {
console.warn(`Invalid label: "${label}" - must be alphanumeric, 1-36 characters`);
}
return isValid;
});
if (validLabels.length !== labels.length) {
throw new Error(`Invalid labels detected. Labels must be alphanumeric (A-Z, a-z, 0-9) and 1-36 characters long. Invalid: ${labels.filter((l: string) => !/^[A-Za-z0-9]{1,36}$/.test(l)).join(', ')}`);
}
const user = await this.users.updateLabels(userId, validLabels) as any;
return {
content: [
{
type: "text",
text: `User ${userId} labels updated:\n${labels.join(', ')}`
text: `User ${userId} labels updated:\n${validLabels.join(', ')}`
}
]
};
@@ -3178,20 +3192,42 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
const { userId } = args;
const identities = await this.users.listIdentities(userId) as any;
const identityList = identities.identities.map((identity: any) =>
`- Provider: ${identity.provider} - ID: ${identity.$id}`
).join('\n');
return {
content: [
{
type: "text",
text: `User ${userId} identities (${identities.total}):\n${identityList}`
try {
// Try without queries first
let identities;
try {
identities = await this.users.listIdentities(userId) as any;
} catch (error: any) {
if (error.message.includes('queries')) {
// If queries error, try with empty array
identities = await (this.users as any).listIdentities(userId, []);
} else {
throw error;
}
]
};
}
const identityList = identities.identities.map((identity: any) =>
`- Provider: ${identity.provider} - ID: ${identity.$id}`
).join('\n');
return {
content: [
{
type: "text",
text: `User ${userId} identities (${identities.total}):\n${identityList}`
}
]
};
} catch (error: any) {
return {
content: [
{
type: "text",
text: `Error listing identities: ${error.message}`
}
]
};
}
}
private async deleteUserIdentity(args: any) {
@@ -3525,13 +3561,16 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
throw new Error(`File not found: ${filePath}`);
}
// For now, return a message indicating file upload capability
// In a full implementation, we would use InputFile.fromPath()
// 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 upload prepared for bucket ${bucketId}:\n- Target ID: ${fid}\n- Source: ${filePath}\n- Note: File upload requires InputFile implementation`
text: `File uploaded successfully:\n- File ID: ${result.$id}\n- Name: ${result.name}\n- Size: ${result.sizeOriginal} bytes\n- MIME Type: ${result.mimeType}`
}
]
};
@@ -3540,7 +3579,7 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
content: [
{
type: "text",
text: `Error preparing file upload: ${error.message}`
text: `Error uploading file: ${error.message}`
}
]
};
@@ -3910,30 +3949,109 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
const { providerId, name, type, enabled } = args;
const pid = providerId || ID.unique();
// Note: Messaging provider creation depends on the specific provider type
// For now, return a placeholder response
return {
content: [
{
type: "text",
text: `Messaging provider placeholder created:\n- ID: ${pid}\n- Name: ${name}\n- Type: ${type}\n- Note: Use specific provider methods (createFcmProvider, createMailgunProvider, etc.)`
}
]
};
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");
// Note: Use specific provider list methods (listFcmProviders, listMailgunProviders, etc.)
return {
content: [
{
type: "text",
text: `Messaging providers: Use specific provider methods (listFcmProviders, listMailgunProviders, etc.)`
}
]
};
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) {
@@ -3958,16 +4076,66 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
const { providerId, name, enabled } = args;
// Note: Use specific provider update methods (updateFcmProvider, updateMailgunProvider, etc.)
try {
// First get the provider to determine its type
const provider = await this.messaging.getProvider(providerId) as any;
return {
content: [
{
type: "text",
text: `Messaging provider ${providerId} updated successfully`
}
]
};
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) {
@@ -3993,15 +4161,45 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
const { messageId, subject, content, topics, users, targets } = args;
const mid = messageId || ID.unique();
// Note: Use createEmail, createSms, or createPush methods
return {
content: [
{
type: "text",
text: `Message placeholder created:\n- ID: ${mid}\n- Subject: ${subject || 'No subject'}\n- Note: Use specific message methods (createEmail, createSms, createPush)`
}
]
};
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) {
@@ -4245,9 +4443,11 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
const countries = await this.locale.listCountries() as any;
const countryList = countries.countries.map((country: any) =>
`- ${country.name} (${country.code}) - Phone: +${country.countryCode}`
).join('\n');
const countryList = countries.countries.map((country: any) => {
// Try to find the actual numeric phone code property
const phoneCode = country.phoneCode || country.dialCode || country.countryCode || country.callingCode || country.dialingCode;
return `- ${country.name} (${country.code}) - Phone: +${phoneCode || 'N/A'}`;
}).join('\n');
return {
content: [
@@ -4319,20 +4519,59 @@ ${attribute.default !== undefined ? `- Default: ${attribute.default}` : ''}`
private async listPhoneCodes() {
if (!this.locale) throw new Error("Locale service not initialized");
// Use listCountries which includes phone codes
try {
// Try using the dedicated listPhones API if available
const phones = await (this.locale as any).listPhones() as any;
if (phones && phones.phones) {
const phoneCodeList = phones.phones.map((phone: any) =>
`- ${phone.countryName}: +${phone.countryCode}`
).join('\n');
return {
content: [
{
type: "text",
text: `Phone codes (${phones.phones.length}):\n${phoneCodeList}`
}
]
};
}
} catch (error) {
// Fall back to countries endpoint
}
// Fallback: Use listCountries
const countries = await this.locale.listCountries() as any;
const phoneCodeList = countries.countries
.filter((country: any) => country.countryCode)
.map((country: any) =>
`- ${country.name}: +${country.countryCode}`
).join('\n');
// Debug: Check actual structure
if (countries.countries && countries.countries.length > 0) {
const sampleCountry = countries.countries[0];
const availableProps = Object.keys(sampleCountry).join(', ');
const phoneCodeList = countries.countries
.map((country: any) => {
const phoneCode = country.phoneCode || country.dialCode || country.countryCode || country.callingCode;
return phoneCode ? `- ${country.name}: +${phoneCode}` : null;
})
.filter(Boolean)
.join('\n');
return {
content: [
{
type: "text",
text: `Phone codes (${countries.countries.length}):\n${phoneCodeList || 'No phone codes found'}\n\nDebug - Sample country properties: ${availableProps}`
}
]
};
}
return {
content: [
{
type: "text",
text: `Phone codes (${countries.countries.length}):\n${phoneCodeList}`
text: `No countries data found. Response structure: ${JSON.stringify(countries, null, 2)}`
}
]
};
@@ -5956,16 +6195,23 @@ For accurate usage tracking, consider:
const { sessionId } = args;
await this.account.deleteSession(sessionId);
try {
await this.account.deleteSession(sessionId);
return {
content: [
{
type: "text",
text: `Session ${sessionId} deleted successfully`
}
]
};
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) {
@@ -6064,13 +6310,13 @@ For accurate usage tracking, consider:
private async getHealthDb() {
if (!this.health) throw new Error("Health service not initialized");
const health = await this.health.getDB();
const health = await this.health.getDB() as any;
return {
content: [
{
type: "text",
text: `Database Health: ${health.status}\n- Ping: ${health.ping}ms`
text: `Database Health: ${health.status || 'OK'}\n- Ping: ${health.ping || health.duration || 'N/A'}ms`
}
]
};

View File

@@ -1,8 +1,8 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"module": "Node16",
"moduleResolution": "node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,