fixed 3 tools
This commit is contained in:
414
src/index.ts
414
src/index.ts
@@ -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.)
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Messaging provider ${providerId} updated successfully`
|
||||
}
|
||||
]
|
||||
};
|
||||
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) {
|
||||
@@ -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);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Session ${sessionId} deleted successfully`
|
||||
}
|
||||
]
|
||||
};
|
||||
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) {
|
||||
@@ -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`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "node16",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
|
||||
Reference in New Issue
Block a user