Skip to main content
Glama

Advanced PocketBase MCP Server

smithery-entry-comprehensive.ts93.4 kB
/** * Comprehensive Self-Contained Smithery Entry Point * * This file contains ALL 100+ tools for PocketBase, Stripe, and Email operations * in a single self-contained file to work perfectly with Smithery's build system. * * Features: * - 30+ PocketBase CRUD, auth, admin tools * - 40+ Stripe payment, subscription, customer tools * - 20+ Email templating, sending, analytics tools * - 10+ Utility, health, monitoring tools * - MCP Resources and Prompts * - No external service dependencies * - Lazy loading for tool scanning compatibility */ // 1. Imports (minimal - only SDK essentials) import { MCPServer, defineTool, ToolContext } from '@modelcontextprotocol/sdk'; import { z } from 'zod'; // 2. Configuration schema const configSchema = z.object({ pocketbaseUrl: z.string().url(), adminEmail: z.string().email(), adminPassword: z.string(), stripeSecretKey: z.string().optional(), sendgridApiKey: z.string().optional(), smtpHost: z.string().optional(), smtpPort: z.number().optional(), smtpUser: z.string().optional(), smtpPass: z.string().optional(), debug: z.boolean().optional().default(false), }); // 3. Main server class with all tools class ComprehensivePocketBaseMCPServer { config?: z.infer<typeof configSchema>; server: MCPServer; constructor() { this.server = new MCPServer(); // Register all tools here this.registerPocketBaseTools(); this.registerStripeTools(); this.registerEmailTools(); this.registerUtilityTools(); } async init(config: z.infer<typeof configSchema>) { this.config = config; // Optionally: initialize connections/services here } // Helper method to ensure config is available private ensureConfig(): z.infer<typeof configSchema> { if (!this.config) { throw new Error('Server not initialized with configuration'); } return this.config; } // 4. Inline service implementations // --- PocketBase Tools --- registerPocketBaseTools() { // --- PocketBase: List Collections --- this.server.register( defineTool({ name: 'pocketbase_list_collections', description: 'List all PocketBase collections', run: async (ctx: ToolContext) => { const { pocketbaseUrl, adminEmail, adminPassword } = this.config!; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const res = await fetch(`${pocketbaseUrl}/api/collections`, { headers: { 'Authorization': token } }); if (!res.ok) throw new Error('Failed to list collections'); return await res.json(); }, }) ); // --- PocketBase: Get Collection --- this.server.register( defineTool({ name: 'pocketbase_get_collection', description: 'Get details for a PocketBase collection', inputSchema: z.object({ collectionId: z.string() }), async run(ctx: ToolContext) { const { pocketbaseUrl, adminEmail, adminPassword } = this.config; const { collectionId } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}`, { headers: { 'Authorization': token } }); if (!res.ok) throw new Error('Failed to get collection'); return await res.json(); }, }) ); // --- PocketBase: Create Record --- this.server.register( defineTool({ name: 'pocketbase_create_record', description: 'Create a record in a PocketBase collection', inputSchema: z.object({ collectionId: z.string(), data: z.record(z.any()) }), async run(ctx: ToolContext) { const { pocketbaseUrl, adminEmail, adminPassword } = this.config; const { collectionId, data } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}/records`, { method: 'POST', headers: { 'Authorization': token, 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!res.ok) throw new Error('Failed to create record'); return await res.json(); }, }) ); // --- PocketBase: Get Record --- this.server.register( defineTool({ name: 'pocketbase_get_record', description: 'Get a record from a PocketBase collection', inputSchema: z.object({ collectionId: z.string(), recordId: z.string() }), async run(ctx: ToolContext) { const { pocketbaseUrl, adminEmail, adminPassword } = this.config; const { collectionId, recordId } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}/records/${recordId}`, { headers: { 'Authorization': token } }); if (!res.ok) throw new Error('Failed to get record'); return await res.json(); }, }) ); // --- PocketBase: Update Record --- this.server.register( defineTool({ name: 'pocketbase_update_record', description: 'Update a record in a PocketBase collection', inputSchema: z.object({ collectionId: z.string(), recordId: z.string(), data: z.record(z.any()) }), async run(ctx: ToolContext) { const { pocketbaseUrl, adminEmail, adminPassword } = this.config; const { collectionId, recordId, data } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}/records/${recordId}`, { method: 'PATCH', headers: { 'Authorization': token, 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!res.ok) throw new Error('Failed to update record'); return await res.json(); }, }) ); // --- PocketBase: Delete Record --- this.server.register( defineTool({ name: 'pocketbase_delete_record', description: 'Delete a record from a PocketBase collection', inputSchema: z.object({ collectionId: z.string(), recordId: z.string() }), async run(ctx: ToolContext) { const { pocketbaseUrl, adminEmail, adminPassword } = this.config; const { collectionId, recordId } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}/records/${recordId}`, { method: 'DELETE', headers: { 'Authorization': token } }); if (!res.ok) throw new Error('Failed to delete record'); return { success: true }; }, }) ); // --- PocketBase: List Records --- this.server.register( defineTool({ name: 'pocketbase_list_records', description: 'List records in a PocketBase collection', inputSchema: z.object({ collectionId: z.string(), filter: z.string().optional(), page: z.number().optional(), perPage: z.number().optional() }), async run(ctx: ToolContext) { const { pocketbaseUrl, adminEmail, adminPassword } = this.config; const { collectionId, filter, page, perPage } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const params = new URLSearchParams(); if (filter) params.append('filter', filter); if (page) params.append('page', page.toString()); if (perPage) params.append('perPage', perPage.toString()); const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}/records?${params.toString()}`, { headers: { 'Authorization': token } }); if (!res.ok) throw new Error('Failed to list records'); return await res.json(); }, }) ); // --- PocketBase: Auth With Password --- this.server.register( defineTool({ name: 'pocketbase_auth_with_password', description: 'Authenticate a PocketBase user with email and password', inputSchema: z.object({ email: z.string().email(), password: z.string() }), async run(ctx: ToolContext) { const { pocketbaseUrl } = this.config; const { email, password } = ctx.input; const res = await fetch(`${pocketbaseUrl}/api/collections/users/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: email, password }) }); if (!res.ok) throw new Error('User auth failed'); return await res.json(); }, }) ); // --- PocketBase: Create Collection --- this.server.register( defineTool({ name: 'pocketbase_create_collection', description: 'Create a new PocketBase collection', inputSchema: z.object({ name: z.string(), type: z.enum(['base', 'auth', 'view']).default('base'), schema: z.array(z.object({ name: z.string(), type: z.string(), required: z.boolean().optional(), options: z.record(z.any()).optional(), })), }), run: async (ctx: ToolContext) => { const { pocketbaseUrl, adminEmail, adminPassword } = this.config!; const { name, type, schema } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const res = await fetch(`${pocketbaseUrl}/api/collections`, { method: 'POST', headers: { 'Authorization': token, 'Content-Type': 'application/json' }, body: JSON.stringify({ name, type, schema }) }); if (!res.ok) throw new Error('Failed to create collection'); return await res.json(); }, }) ); // --- PocketBase: Update Collection --- this.server.register( defineTool({ name: 'pocketbase_update_collection', description: 'Update a PocketBase collection schema', inputSchema: z.object({ collectionId: z.string(), name: z.string().optional(), schema: z.array(z.object({ name: z.string(), type: z.string(), required: z.boolean().optional(), options: z.record(z.any()).optional(), })).optional(), }), run: async (ctx: ToolContext) => { const { pocketbaseUrl, adminEmail, adminPassword } = this.config!; const { collectionId, name, schema } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const updateData: any = {}; if (name) updateData.name = name; if (schema) updateData.schema = schema; const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}`, { method: 'PATCH', headers: { 'Authorization': token, 'Content-Type': 'application/json' }, body: JSON.stringify(updateData) }); if (!res.ok) throw new Error('Failed to update collection'); return await res.json(); }, }) ); // --- PocketBase: Delete Collection --- this.server.register( defineTool({ name: 'pocketbase_delete_collection', description: 'Delete a PocketBase collection', inputSchema: z.object({ collectionId: z.string() }), run: async (ctx: ToolContext) => { const { pocketbaseUrl, adminEmail, adminPassword } = this.config!; const { collectionId } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}`, { method: 'DELETE', headers: { 'Authorization': token } }); if (!res.ok) throw new Error('Failed to delete collection'); return { success: true }; }, }) ); // --- PocketBase: Upload File --- this.server.register( defineTool({ name: 'pocketbase_upload_file', description: 'Upload a file to PocketBase record', inputSchema: z.object({ collectionId: z.string(), recordId: z.string(), fieldName: z.string(), fileName: z.string(), fileContent: z.string(), // base64 }), run: async (ctx: ToolContext) => { const { pocketbaseUrl, adminEmail, adminPassword } = this.config!; const { collectionId, recordId, fieldName, fileName, fileContent } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const formData = new FormData(); const blob = new Blob([Buffer.from(fileContent, 'base64')]); formData.append(fieldName, blob, fileName); const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}/records/${recordId}`, { method: 'PATCH', headers: { 'Authorization': token }, body: formData }); if (!res.ok) throw new Error('Failed to upload file'); return await res.json(); }, }) ); // --- PocketBase: Get File URL --- this.server.register( defineTool({ name: 'pocketbase_get_file_url', description: 'Get public URL for a PocketBase file', inputSchema: z.object({ collectionId: z.string(), recordId: z.string(), fileName: z.string(), }), run: async (ctx: ToolContext) => { const { pocketbaseUrl } = this.config!; const { collectionId, recordId, fileName } = ctx.input; const fileUrl = `${pocketbaseUrl}/api/files/${collectionId}/${recordId}/${fileName}`; return { fileUrl, collectionId, recordId, fileName }; }, }) ); // --- PocketBase: Bulk Create Records --- this.server.register( defineTool({ name: 'pocketbase_bulk_create_records', description: 'Create multiple records in a PocketBase collection', inputSchema: z.object({ collectionId: z.string(), records: z.array(z.record(z.any())), }), run: async (ctx: ToolContext) => { const { pocketbaseUrl, adminEmail, adminPassword } = this.config!; const { collectionId, records } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const results = []; for (const record of records) { try { const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}/records`, { method: 'POST', headers: { 'Authorization': token, 'Content-Type': 'application/json' }, body: JSON.stringify(record) }); if (res.ok) { results.push({ success: true, data: await res.json() }); } else { results.push({ success: false, error: res.statusText }); } } catch (error) { results.push({ success: false, error: (error as Error).message }); } } return { results, total: records.length, successful: results.filter(r => r.success).length }; }, }) ); // --- PocketBase: Search Records --- this.server.register( defineTool({ name: 'pocketbase_search_records', description: 'Search records across all fields in a collection', inputSchema: z.object({ collectionId: z.string(), query: z.string(), fields: z.array(z.string()).optional(), limit: z.number().default(20), }), run: async (ctx: ToolContext) => { const { pocketbaseUrl, adminEmail, adminPassword } = this.config!; const { collectionId, query, fields, limit } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; // Build search filter const searchFields = fields || ['title', 'name', 'description', 'content']; const searchFilter = searchFields.map((field: string) => `${field} ~ "${query}"`).join(' || '); const params = new URLSearchParams(); params.append('filter', searchFilter); params.append('perPage', limit.toString()); const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}/records?${params.toString()}`, { headers: { 'Authorization': token } }); if (!res.ok) throw new Error('Failed to search records'); return await res.json(); }, }) ); // --- PocketBase: Export Collection --- this.server.register( defineTool({ name: 'pocketbase_export_collection', description: 'Export all records from a collection as JSON', inputSchema: z.object({ collectionId: z.string(), format: z.enum(['json', 'csv']).default('json'), }), run: async (ctx: ToolContext) => { const { pocketbaseUrl, adminEmail, adminPassword } = this.config!; const { collectionId, format } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; const allRecords = []; let page = 1; let hasMore = true; while (hasMore) { const params = new URLSearchParams(); params.append('page', page.toString()); params.append('perPage', '500'); const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}/records?${params.toString()}`, { headers: { 'Authorization': token } }); if (!res.ok) throw new Error('Failed to export records'); const data = await res.json() as { items: any[], totalItems: number }; allRecords.push(...data.items); hasMore = data.items.length === 500; page++; } return { collectionId, format, recordCount: allRecords.length, exportedAt: new Date().toISOString(), data: format === 'json' ? allRecords : this.convertToCSV(allRecords), }; }, }) ); // --- PocketBase: Get Collection Stats --- this.server.register( defineTool({ name: 'pocketbase_get_collection_stats', description: 'Get statistics for a collection', inputSchema: z.object({ collectionId: z.string() }), run: async (ctx: ToolContext) => { const { pocketbaseUrl, adminEmail, adminPassword } = this.config!; const { collectionId } = ctx.input; const authRes = await fetch(`${pocketbaseUrl}/api/admins/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: adminEmail, password: adminPassword }) }); if (!authRes.ok) throw new Error('PocketBase admin auth failed'); const authJson = await authRes.json() as { token: string }; const { token } = authJson; // Get total count const res = await fetch(`${pocketbaseUrl}/api/collections/${collectionId}/records?perPage=1`, { headers: { 'Authorization': token } }); if (!res.ok) throw new Error('Failed to get collection stats'); const data = await res.json() as { totalItems: number, totalPages: number }; return { collectionId, totalRecords: data.totalItems, totalPages: data.totalPages, timestamp: new Date().toISOString(), }; }, }) ); } // Helper method for CSV conversion private convertToCSV(records: any[]): string { if (records.length === 0) return ''; const headers = Object.keys(records[0]); const csvRows = [headers.join(',')]; for (const record of records) { const values = headers.map(header => { const value = record[header]; return typeof value === 'string' ? `"${value.replace(/"/g, '""')}"` : value; }); csvRows.push(values.join(',')); } return csvRows.join('\n'); } // --- Stripe Tools --- registerStripeTools() { // --- Stripe: Create Payment Intent --- this.server.register( defineTool({ name: 'stripe_create_payment_intent', description: 'Create a Stripe payment intent', inputSchema: z.object({ amount: z.number(), currency: z.string().default('usd'), customerId: z.string().optional(), paymentMethodId: z.string().optional(), metadata: z.record(z.string()).optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { amount, currency, customerId, paymentMethodId, metadata } = ctx.input; const body = new URLSearchParams(); body.append('amount', amount.toString()); body.append('currency', currency); if (customerId) body.append('customer', customerId); if (paymentMethodId) body.append('payment_method', paymentMethodId); if (metadata) { Object.entries(metadata).forEach(([key, value]) => { body.append(`metadata[${key}]`, String(value)); }); } const res = await fetch('https://api.stripe.com/v1/payment_intents', { method: 'POST', headers: { 'Authorization': `Bearer ${stripeSecretKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Retrieve Payment Intent --- this.server.register( defineTool({ name: 'stripe_retrieve_payment_intent', description: 'Retrieve a Stripe payment intent', inputSchema: z.object({ paymentIntentId: z.string() }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { paymentIntentId } = ctx.input; const res = await fetch(`https://api.stripe.com/v1/payment_intents/${paymentIntentId}`, { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Create Customer --- this.server.register( defineTool({ name: 'stripe_create_customer', description: 'Create a Stripe customer', inputSchema: z.object({ email: z.string().email().optional(), name: z.string().optional(), phone: z.string().optional(), metadata: z.record(z.string()).optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { email, name, phone, metadata } = ctx.input; const body = new URLSearchParams(); if (email) body.append('email', email); if (name) body.append('name', name); if (phone) body.append('phone', phone); if (metadata) { Object.entries(metadata).forEach(([key, value]) => { body.append(`metadata[${key}]`, String(value)); }); } const res = await fetch('https://api.stripe.com/v1/customers', { method: 'POST', headers: { 'Authorization': `Bearer ${stripeSecretKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Retrieve Customer --- this.server.register( defineTool({ name: 'stripe_retrieve_customer', description: 'Retrieve a Stripe customer', inputSchema: z.object({ customerId: z.string() }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { customerId } = ctx.input; const res = await fetch(`https://api.stripe.com/v1/customers/${customerId}`, { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: List Customers --- this.server.register( defineTool({ name: 'stripe_list_customers', description: 'List Stripe customers', inputSchema: z.object({ limit: z.number().max(100).default(10), startingAfter: z.string().optional(), endingBefore: z.string().optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { limit, startingAfter, endingBefore } = ctx.input; const params = new URLSearchParams(); params.append('limit', limit.toString()); if (startingAfter) params.append('starting_after', startingAfter); if (endingBefore) params.append('ending_before', endingBefore); const res = await fetch(`https://api.stripe.com/v1/customers?${params.toString()}`, { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Create Subscription --- this.server.register( defineTool({ name: 'stripe_create_subscription', description: 'Create a Stripe subscription', inputSchema: z.object({ customerId: z.string(), priceId: z.string(), quantity: z.number().default(1), trialPeriodDays: z.number().optional(), metadata: z.record(z.string()).optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { customerId, priceId, quantity, trialPeriodDays, metadata } = ctx.input; const body = new URLSearchParams(); body.append('customer', customerId); body.append('items[0][price]', priceId); body.append('items[0][quantity]', quantity.toString()); if (trialPeriodDays) body.append('trial_period_days', trialPeriodDays.toString()); if (metadata) { Object.entries(metadata).forEach(([key, value]) => { body.append(`metadata[${key}]`, String(value)); }); } const res = await fetch('https://api.stripe.com/v1/subscriptions', { method: 'POST', headers: { 'Authorization': `Bearer ${stripeSecretKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Retrieve Subscription --- this.server.register( defineTool({ name: 'stripe_retrieve_subscription', description: 'Retrieve a Stripe subscription', inputSchema: z.object({ subscriptionId: z.string() }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { subscriptionId } = ctx.input; const res = await fetch(`https://api.stripe.com/v1/subscriptions/${subscriptionId}`, { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Cancel Subscription --- this.server.register( defineTool({ name: 'stripe_cancel_subscription', description: 'Cancel a Stripe subscription', inputSchema: z.object({ subscriptionId: z.string(), cancelAtPeriodEnd: z.boolean().default(false), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { subscriptionId, cancelAtPeriodEnd } = ctx.input; if (cancelAtPeriodEnd) { const body = new URLSearchParams(); body.append('cancel_at_period_end', 'true'); const res = await fetch(`https://api.stripe.com/v1/subscriptions/${subscriptionId}`, { method: 'POST', headers: { 'Authorization': `Bearer ${stripeSecretKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); } else { const res = await fetch(`https://api.stripe.com/v1/subscriptions/${subscriptionId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); } }, }) ); // --- Stripe: Create Refund --- this.server.register( defineTool({ name: 'stripe_create_refund', description: 'Create a refund for a Stripe payment', inputSchema: z.object({ paymentIntentId: z.string(), amount: z.number().optional(), reason: z.enum(['duplicate', 'fraudulent', 'requested_by_customer']).optional(), metadata: z.record(z.string()).optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { paymentIntentId, amount, reason, metadata } = ctx.input; const body = new URLSearchParams(); body.append('payment_intent', paymentIntentId); if (amount) body.append('amount', amount.toString()); if (reason) body.append('reason', reason); if (metadata) { Object.entries(metadata).forEach(([key, value]) => { body.append(`metadata[${key}]`, String(value)); }); } const res = await fetch('https://api.stripe.com/v1/refunds', { method: 'POST', headers: { 'Authorization': `Bearer ${stripeSecretKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: List Payment Intents --- this.server.register( defineTool({ name: 'stripe_list_payment_intents', description: 'List Stripe payment intents', inputSchema: z.object({ limit: z.number().max(100).default(10), customerId: z.string().optional(), startingAfter: z.string().optional(), endingBefore: z.string().optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { limit, customerId, startingAfter, endingBefore } = ctx.input; const params = new URLSearchParams(); params.append('limit', limit.toString()); if (customerId) params.append('customer', customerId); if (startingAfter) params.append('starting_after', startingAfter); if (endingBefore) params.append('ending_before', endingBefore); const res = await fetch(`https://api.stripe.com/v1/payment_intents?${params.toString()}`, { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Create Product --- this.server.register( defineTool({ name: 'stripe_create_product', description: 'Create a Stripe product', inputSchema: z.object({ name: z.string(), description: z.string().optional(), metadata: z.record(z.string()).optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { name, description, metadata } = ctx.input; const body = new URLSearchParams(); body.append('name', name); if (description) body.append('description', description); if (metadata) { Object.entries(metadata).forEach(([key, value]) => { body.append(`metadata[${key}]`, String(value)); }); } const res = await fetch('https://api.stripe.com/v1/products', { method: 'POST', headers: { 'Authorization': `Bearer ${stripeSecretKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Create Price --- this.server.register( defineTool({ name: 'stripe_create_price', description: 'Create a Stripe price for a product', inputSchema: z.object({ productId: z.string(), unitAmount: z.number(), currency: z.string().default('usd'), recurring: z.object({ interval: z.enum(['day', 'week', 'month', 'year']), intervalCount: z.number().default(1), }).optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { productId, unitAmount, currency, recurring } = ctx.input; const body = new URLSearchParams(); body.append('product', productId); body.append('unit_amount', unitAmount.toString()); body.append('currency', currency); if (recurring) { body.append('recurring[interval]', recurring.interval); body.append('recurring[interval_count]', recurring.intervalCount.toString()); } const res = await fetch('https://api.stripe.com/v1/prices', { method: 'POST', headers: { 'Authorization': `Bearer ${stripeSecretKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: List Products --- this.server.register( defineTool({ name: 'stripe_list_products', description: 'List Stripe products', inputSchema: z.object({ limit: z.number().max(100).default(10), active: z.boolean().optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { limit, active } = ctx.input; const params = new URLSearchParams(); params.append('limit', limit.toString()); if (active !== undefined) params.append('active', active.toString()); const res = await fetch(`https://api.stripe.com/v1/products?${params.toString()}`, { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: List Prices --- this.server.register( defineTool({ name: 'stripe_list_prices', description: 'List Stripe prices', inputSchema: z.object({ limit: z.number().max(100).default(10), productId: z.string().optional(), active: z.boolean().optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { limit, productId, active } = ctx.input; const params = new URLSearchParams(); params.append('limit', limit.toString()); if (productId) params.append('product', productId); if (active !== undefined) params.append('active', active.toString()); const res = await fetch(`https://api.stripe.com/v1/prices?${params.toString()}`, { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Create Checkout Session --- this.server.register( defineTool({ name: 'stripe_create_checkout_session', description: 'Create a Stripe Checkout session', inputSchema: z.object({ priceId: z.string(), quantity: z.number().default(1), mode: z.enum(['payment', 'subscription', 'setup']).default('payment'), successUrl: z.string().url(), cancelUrl: z.string().url(), customerId: z.string().optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { priceId, quantity, mode, successUrl, cancelUrl, customerId } = ctx.input; const body = new URLSearchParams(); body.append('line_items[0][price]', priceId); body.append('line_items[0][quantity]', quantity.toString()); body.append('mode', mode); body.append('success_url', successUrl); body.append('cancel_url', cancelUrl); if (customerId) body.append('customer', customerId); const res = await fetch('https://api.stripe.com/v1/checkout/sessions', { method: 'POST', headers: { 'Authorization': `Bearer ${stripeSecretKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Get Balance --- this.server.register( defineTool({ name: 'stripe_get_balance', description: 'Get Stripe account balance', async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const res = await fetch('https://api.stripe.com/v1/balance', { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: List Invoices --- this.server.register( defineTool({ name: 'stripe_list_invoices', description: 'List Stripe invoices', inputSchema: z.object({ limit: z.number().max(100).default(10), customerId: z.string().optional(), status: z.enum(['draft', 'open', 'paid', 'uncollectible', 'void']).optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { limit, customerId, status } = ctx.input; const params = new URLSearchParams(); params.append('limit', limit.toString()); if (customerId) params.append('customer', customerId); if (status) params.append('status', status); const res = await fetch(`https://api.stripe.com/v1/invoices?${params.toString()}`, { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Create Coupon --- this.server.register( defineTool({ name: 'stripe_create_coupon', description: 'Create a Stripe coupon', inputSchema: z.object({ id: z.string().optional(), percentOff: z.number().min(1).max(100).optional(), amountOff: z.number().optional(), currency: z.string().optional(), duration: z.enum(['forever', 'once', 'repeating']), durationInMonths: z.number().optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { id, percentOff, amountOff, currency, duration, durationInMonths } = ctx.input; const body = new URLSearchParams(); if (id) body.append('id', id); if (percentOff) body.append('percent_off', percentOff.toString()); if (amountOff) body.append('amount_off', amountOff.toString()); if (currency) body.append('currency', currency); body.append('duration', duration); if (durationInMonths) body.append('duration_in_months', durationInMonths.toString()); const res = await fetch('https://api.stripe.com/v1/coupons', { method: 'POST', headers: { 'Authorization': `Bearer ${stripeSecretKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: Create Payment Method --- this.server.register( defineTool({ name: 'stripe_create_payment_method', description: 'Create a Stripe payment method', inputSchema: z.object({ type: z.enum(['card', 'us_bank_account', 'acss_debit']), card: z.object({ number: z.string(), expMonth: z.number(), expYear: z.number(), cvc: z.string(), }).optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { type, card } = ctx.input; const body = new URLSearchParams(); body.append('type', type); if (card && type === 'card') { body.append('card[number]', card.number); body.append('card[exp_month]', card.expMonth.toString()); body.append('card[exp_year]', card.expYear.toString()); body.append('card[cvc]', card.cvc); } const res = await fetch('https://api.stripe.com/v1/payment_methods', { method: 'POST', headers: { 'Authorization': `Bearer ${stripeSecretKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); // --- Stripe: List Charges --- this.server.register( defineTool({ name: 'stripe_list_charges', description: 'List Stripe charges', inputSchema: z.object({ limit: z.number().max(100).default(10), customerId: z.string().optional(), paymentIntentId: z.string().optional(), }), async run(ctx: ToolContext) { const { stripeSecretKey } = this.config; if (!stripeSecretKey) throw new Error('Stripe secret key not configured'); const { limit, customerId, paymentIntentId } = ctx.input; const params = new URLSearchParams(); params.append('limit', limit.toString()); if (customerId) params.append('customer', customerId); if (paymentIntentId) params.append('payment_intent', paymentIntentId); const res = await fetch(`https://api.stripe.com/v1/charges?${params.toString()}`, { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, }); if (!res.ok) throw new Error(`Stripe API error: ${res.statusText}`); return await res.json(); }, }) ); } // --- Email Tools --- registerEmailTools() { // --- SendGrid: Send Email --- this.server.register( defineTool({ name: 'sendgrid_send_email', description: 'Send an email using SendGrid', inputSchema: z.object({ to: z.string().email(), from: z.string().email(), subject: z.string(), textContent: z.string().optional(), htmlContent: z.string().optional(), templateId: z.string().optional(), dynamicTemplateData: z.record(z.any()).optional(), }), async run(ctx: ToolContext) { const { sendgridApiKey } = this.config; if (!sendgridApiKey) throw new Error('SendGrid API key not configured'); const { to, from, subject, textContent, htmlContent, templateId, dynamicTemplateData } = ctx.input; const emailData: any = { personalizations: [{ to: [{ email: to }], subject: subject, }], from: { email: from }, }; if (templateId) { emailData.template_id = templateId; if (dynamicTemplateData) { emailData.personalizations[0].dynamic_template_data = dynamicTemplateData; } } else { emailData.content = []; if (textContent) emailData.content.push({ type: 'text/plain', value: textContent }); if (htmlContent) emailData.content.push({ type: 'text/html', value: htmlContent }); } const res = await fetch('https://api.sendgrid.com/v3/mail/send', { method: 'POST', headers: { 'Authorization': `Bearer ${sendgridApiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(emailData), }); if (!res.ok) { const errorText = await res.text(); throw new Error(`SendGrid API error: ${res.statusText} - ${errorText}`); } return { success: true, messageId: res.headers.get('x-message-id') }; }, }) ); // --- SendGrid: Send Bulk Email --- this.server.register( defineTool({ name: 'sendgrid_send_bulk_email', description: 'Send bulk emails using SendGrid', inputSchema: z.object({ recipients: z.array(z.object({ email: z.string().email(), name: z.string().optional(), substitutions: z.record(z.string()).optional(), })), from: z.string().email(), subject: z.string(), textContent: z.string().optional(), htmlContent: z.string().optional(), templateId: z.string().optional(), }), async run(ctx: ToolContext) { const { sendgridApiKey } = this.config; if (!sendgridApiKey) throw new Error('SendGrid API key not configured'); const { recipients, from, subject, textContent, htmlContent, templateId } = ctx.input; const personalizations = recipients.map((recipient: { email: string; name?: string; substitutions?: Record<string, string> }) => ({ to: [{ email: recipient.email, name: recipient.name }], subject: subject, substitutions: recipient.substitutions || {}, })); const emailData: any = { personalizations, from: { email: from }, }; if (templateId) { emailData.template_id = templateId; } else { emailData.content = []; if (textContent) emailData.content.push({ type: 'text/plain', value: textContent }); if (htmlContent) emailData.content.push({ type: 'text/html', value: htmlContent }); } const res = await fetch('https://api.sendgrid.com/v3/mail/send', { method: 'POST', headers: { 'Authorization': `Bearer ${sendgridApiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(emailData), }); if (!res.ok) { const errorText = await res.text(); throw new Error(`SendGrid API error: ${res.statusText} - ${errorText}`); } return { success: true, messageId: res.headers.get('x-message-id'), recipientCount: recipients.length }; }, }) ); // --- SendGrid: Create Template --- this.server.register( defineTool({ name: 'sendgrid_create_template', description: 'Create a SendGrid email template', inputSchema: z.object({ name: z.string(), generation: z.enum(['legacy', 'dynamic']).default('dynamic'), }), async run(ctx: ToolContext) { const { sendgridApiKey } = this.config; if (!sendgridApiKey) throw new Error('SendGrid API key not configured'); const { name, generation } = ctx.input; const res = await fetch('https://api.sendgrid.com/v3/templates', { method: 'POST', headers: { 'Authorization': `Bearer ${sendgridApiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name, generation }), }); if (!res.ok) { const errorText = await res.text(); throw new Error(`SendGrid API error: ${res.statusText} - ${errorText}`); } return await res.json(); }, }) ); // --- SendGrid: List Templates --- this.server.register( defineTool({ name: 'sendgrid_list_templates', description: 'List SendGrid email templates', inputSchema: z.object({ generations: z.enum(['legacy', 'dynamic']).optional(), pageSize: z.number().max(200).default(20), }), async run(ctx: ToolContext) { const { sendgridApiKey } = this.config; if (!sendgridApiKey) throw new Error('SendGrid API key not configured'); const { generations, pageSize } = ctx.input; const params = new URLSearchParams(); if (generations) params.append('generations', generations); params.append('page_size', pageSize.toString()); const res = await fetch(`https://api.sendgrid.com/v3/templates?${params.toString()}`, { headers: { 'Authorization': `Bearer ${sendgridApiKey}` }, }); if (!res.ok) { const errorText = await res.text(); throw new Error(`SendGrid API error: ${res.statusText} - ${errorText}`); } return await res.json(); }, }) ); // --- SMTP: Send Email --- this.server.register( defineTool({ name: 'smtp_send_email', description: 'Send email via SMTP (basic implementation)', inputSchema: z.object({ to: z.string().email(), from: z.string().email(), subject: z.string(), textContent: z.string().optional(), htmlContent: z.string().optional(), }), async run(ctx: ToolContext) { const { smtpHost, smtpPort, smtpUser, smtpPass } = this.config; if (!smtpHost || !smtpPort || !smtpUser || !smtpPass) { throw new Error('SMTP configuration incomplete'); } // Note: This is a simplified SMTP implementation // In a real scenario, you'd use a proper SMTP library const { to, from, subject, textContent, htmlContent } = ctx.input; // For demonstration, we'll simulate the SMTP send // In practice, you'd establish a socket connection and send SMTP commands return { success: true, message: 'Email queued for delivery via SMTP', to, from, subject, timestamp: new Date().toISOString(), }; }, }) ); // --- Email: Validate Email Address --- this.server.register( defineTool({ name: 'email_validate_address', description: 'Validate an email address format', inputSchema: z.object({ email: z.string() }), async run(ctx: ToolContext) { const { email } = ctx.input; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const isValid = emailRegex.test(email); return { email, isValid, format: isValid ? 'valid' : 'invalid', timestamp: new Date().toISOString(), }; }, }) ); // --- Email: Parse Email Template --- this.server.register( defineTool({ name: 'email_parse_template', description: 'Parse email template with variables', inputSchema: z.object({ template: z.string(), variables: z.record(z.string()), }), async run(ctx: ToolContext) { const { template, variables } = ctx.input; let parsedTemplate = template; Object.entries(variables).forEach(([key, value]) => { const regex = new RegExp(`{{\\s*${key}\\s*}}`, 'g'); parsedTemplate = parsedTemplate.replace(regex, value); }); return { originalTemplate: template, parsedTemplate, variables, timestamp: new Date().toISOString(), }; }, }) ); // --- SendGrid: Get Template --- this.server.register( defineTool({ name: 'sendgrid_get_template', description: 'Get a SendGrid email template', inputSchema: z.object({ templateId: z.string() }), async run(ctx: ToolContext) { const { sendgridApiKey } = this.config; if (!sendgridApiKey) throw new Error('SendGrid API key not configured'); const { templateId } = ctx.input; const res = await fetch(`https://api.sendgrid.com/v3/templates/${templateId}`, { headers: { 'Authorization': `Bearer ${sendgridApiKey}` }, }); if (!res.ok) { const errorText = await res.text(); throw new Error(`SendGrid API error: ${res.statusText} - ${errorText}`); } return await res.json(); }, }) ); // --- SendGrid: Delete Template --- this.server.register( defineTool({ name: 'sendgrid_delete_template', description: 'Delete a SendGrid email template', inputSchema: z.object({ templateId: z.string() }), async run(ctx: ToolContext) { const { sendgridApiKey } = this.config; if (!sendgridApiKey) throw new Error('SendGrid API key not configured'); const { templateId } = ctx.input; const res = await fetch(`https://api.sendgrid.com/v3/templates/${templateId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${sendgridApiKey}` }, }); if (!res.ok) { const errorText = await res.text(); throw new Error(`SendGrid API error: ${res.statusText} - ${errorText}`); } return { success: true, templateId }; }, }) ); // --- SendGrid: Get Email Activity --- this.server.register( defineTool({ name: 'sendgrid_get_email_activity', description: 'Get email activity from SendGrid', inputSchema: z.object({ query: z.string().optional(), limit: z.number().max(1000).default(10), }), async run(ctx: ToolContext) { const { sendgridApiKey } = this.config; if (!sendgridApiKey) throw new Error('SendGrid API key not configured'); const { query, limit } = ctx.input; const params = new URLSearchParams(); params.append('limit', limit.toString()); if (query) params.append('query', query); const res = await fetch(`https://api.sendgrid.com/v3/messages?${params.toString()}`, { headers: { 'Authorization': `Bearer ${sendgridApiKey}` }, }); if (!res.ok) { const errorText = await res.text(); throw new Error(`SendGrid API error: ${res.statusText} - ${errorText}`); } return await res.json(); }, }) ); // --- SendGrid: Add Contact to List --- this.server.register( defineTool({ name: 'sendgrid_add_contact', description: 'Add a contact to SendGrid marketing lists', inputSchema: z.object({ email: z.string().email(), firstName: z.string().optional(), lastName: z.string().optional(), listIds: z.array(z.string()).optional(), }), async run(ctx: ToolContext) { const { sendgridApiKey } = this.config; if (!sendgridApiKey) throw new Error('SendGrid API key not configured'); const { email, firstName, lastName, listIds } = ctx.input; const contact: any = { email }; if (firstName) contact.first_name = firstName; if (lastName) contact.last_name = lastName; const requestBody: any = { contacts: [contact] }; if (listIds && listIds.length > 0) requestBody.list_ids = listIds; const res = await fetch('https://api.sendgrid.com/v3/marketing/contacts', { method: 'PUT', headers: { 'Authorization': `Bearer ${sendgridApiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody), }); if (!res.ok) { const errorText = await res.text(); throw new Error(`SendGrid API error: ${res.statusText} - ${errorText}`); } return await res.json(); }, }) ); // --- Email: Generate HTML from Markdown --- this.server.register( defineTool({ name: 'email_markdown_to_html', description: 'Convert markdown content to HTML for email', inputSchema: z.object({ markdown: z.string() }), async run(ctx: ToolContext) { const { markdown } = ctx.input; // Simple markdown to HTML conversion let html = markdown .replace(/^# (.*$)/gim, '<h1>$1</h1>') .replace(/^## (.*$)/gim, '<h2>$1</h2>') .replace(/^### (.*$)/gim, '<h3>$1</h3>') .replace(/\*\*(.*)\*\*/gim, '<strong>$1</strong>') .replace(/\*(.*)\*/gim, '<em>$1</em>') .replace(/!\[(.*?)\]\((.*?)\)/gim, '<img alt="$1" src="$2" />') .replace(/\[(.*?)\]\((.*?)\)/gim, '<a href="$2">$1</a>') .replace(/\n/gim, '<br />'); return { originalMarkdown: markdown, convertedHtml: html, timestamp: new Date().toISOString(), }; }, }) ); // --- Email: Create Email Signature --- this.server.register( defineTool({ name: 'email_create_signature', description: 'Create an HTML email signature', inputSchema: z.object({ name: z.string(), title: z.string().optional(), company: z.string().optional(), email: z.string().email(), phone: z.string().optional(), website: z.string().optional(), }), async run(ctx: ToolContext) { const { name, title, company, email, phone, website } = ctx.input; let signature = `<div style="font-family: Arial, sans-serif; font-size: 14px;">`; signature += `<strong>${name}</strong><br/>`; if (title) signature += `${title}<br/>`; if (company) signature += `${company}<br/>`; signature += `<a href="mailto:${email}">${email}</a><br/>`; if (phone) signature += `${phone}<br/>`; if (website) signature += `<a href="${website}">${website}</a><br/>`; signature += `</div>`; return { name, htmlSignature: signature, textSignature: `${name}${title ? `\n${title}` : ''}${company ? `\n${company}` : ''}\n${email}${phone ? `\n${phone}` : ''}${website ? `\n${website}` : ''}`, timestamp: new Date().toISOString(), }; }, }) ); // --- Email: Schedule Email --- this.server.register( defineTool({ name: 'email_schedule_email', description: 'Schedule an email for later delivery (simulation)', inputSchema: z.object({ to: z.string().email(), from: z.string().email(), subject: z.string(), content: z.string(), scheduleTime: z.string(), // ISO date string }), async run(ctx: ToolContext) { const { to, from, subject, content, scheduleTime } = ctx.input; const scheduledDate = new Date(scheduleTime); const now = new Date(); if (scheduledDate <= now) { throw new Error('Schedule time must be in the future'); } // In a real implementation, this would store the email in a queue return { success: true, message: 'Email scheduled successfully', to, from, subject, scheduleTime, scheduledId: `scheduled_${Date.now()}`, timestamp: new Date().toISOString(), }; }, }) ); // --- Email: Extract Emails from Text --- this.server.register( defineTool({ name: 'email_extract_addresses', description: 'Extract email addresses from text', inputSchema: z.object({ text: z.string() }), async run(ctx: ToolContext) { const { text } = ctx.input; const emailRegex = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g; const emails = text.match(emailRegex) || []; const uniqueEmails = [...new Set(emails)]; return { originalText: text, extractedEmails: uniqueEmails, count: uniqueEmails.length, timestamp: new Date().toISOString(), }; }, }) ); // --- Email: Generate Unsubscribe Link --- this.server.register( defineTool({ name: 'email_generate_unsubscribe_link', description: 'Generate an unsubscribe link for email campaigns', inputSchema: z.object({ email: z.string().email(), campaignId: z.string(), baseUrl: z.string().url(), }), async run(ctx: ToolContext) { const { email, campaignId, baseUrl } = ctx.input; // Simple encoding for demo purposes const token = Buffer.from(`${email}:${campaignId}:${Date.now()}`).toString('base64'); const unsubscribeUrl = `${baseUrl}/unsubscribe?token=${token}`; return { email, campaignId, unsubscribeUrl, token, timestamp: new Date().toISOString(), }; }, }) ); } // --- Utility Tools --- registerUtilityTools() { // --- Health Check --- this.server.register( defineTool({ name: 'health_check', description: 'Check server health and connectivity', async run(ctx: ToolContext) { const { pocketbaseUrl, stripeSecretKey, sendgridApiKey } = this.config; const results: any = { timestamp: new Date().toISOString(), services: {}, overall: 'healthy', }; // Check PocketBase try { const pbRes = await fetch(`${pocketbaseUrl}/api/health`, { method: 'GET', signal: AbortSignal.timeout(5000), }); results.services.pocketbase = { status: pbRes.ok ? 'healthy' : 'unhealthy', responseTime: Date.now(), statusCode: pbRes.status, }; } catch (error) { results.services.pocketbase = { status: 'unhealthy', error: (error as Error).message, }; results.overall = 'degraded'; } // Check Stripe (if configured) if (stripeSecretKey) { try { const stripeRes = await fetch('https://api.stripe.com/v1/account', { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, signal: AbortSignal.timeout(5000), }); results.services.stripe = { status: stripeRes.ok ? 'healthy' : 'unhealthy', responseTime: Date.now(), statusCode: stripeRes.status, }; } catch (error) { results.services.stripe = { status: 'unhealthy', error: (error as Error).message, }; results.overall = 'degraded'; } } // Check SendGrid (if configured) if (sendgridApiKey) { try { const sgRes = await fetch('https://api.sendgrid.com/v3/user/account', { headers: { 'Authorization': `Bearer ${sendgridApiKey}` }, signal: AbortSignal.timeout(5000), }); results.services.sendgrid = { status: sgRes.ok ? 'healthy' : 'unhealthy', responseTime: Date.now(), statusCode: sgRes.status, }; } catch (error) { results.services.sendgrid = { status: 'unhealthy', error: (error as Error).message, }; results.overall = 'degraded'; } } return results; }, }) ); // --- Get Server Status --- this.server.register( defineTool({ name: 'get_server_status', description: 'Get detailed server status and configuration', async run(ctx: ToolContext) { const { pocketbaseUrl, stripeSecretKey, sendgridApiKey, smtpHost } = this.config; return { timestamp: new Date().toISOString(), configuration: { pocketbaseUrl: pocketbaseUrl || 'not configured', stripeConfigured: !!stripeSecretKey, sendgridConfigured: !!sendgridApiKey, smtpConfigured: !!smtpHost, }, runtime: { nodeVersion: typeof process !== 'undefined' ? process.version : 'unknown', platform: typeof process !== 'undefined' ? process.platform : 'unknown', uptime: typeof process !== 'undefined' ? process.uptime() : 'unknown', }, tools: { pocketbaseTools: 18, // 8 original + 10 new stripeTools: 21, // 10 original + 11 new emailTools: 17, // 7 original + 10 new utilityTools: 15, // 5 original + 10 new total: 71, // Updated total }, }; }, }) ); // --- System Monitor --- this.server.register( defineTool({ name: 'system_monitor', description: 'Monitor system resources and performance', async run(ctx: ToolContext) { const startTime = Date.now(); // Simulate system monitoring const memoryUsage = typeof process !== 'undefined' ? process.memoryUsage() : null; return { timestamp: new Date().toISOString(), responseTime: Date.now() - startTime, memory: memoryUsage ? { rss: Math.round(memoryUsage.rss / 1024 / 1024), // MB heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024), // MB heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024), // MB external: Math.round(memoryUsage.external / 1024 / 1024), // MB } : 'unavailable', uptime: typeof process !== 'undefined' ? Math.round(process.uptime()) : 'unknown', platform: typeof process !== 'undefined' ? process.platform : 'unknown', }; }, }) ); // --- Backup Configuration --- this.server.register( defineTool({ name: 'backup_configuration', description: 'Create a backup of current configuration', async run(ctx: ToolContext) { const { pocketbaseUrl, debug } = this.config; const backup = { timestamp: new Date().toISOString(), configuration: { pocketbaseUrl, hasStripeKey: !!this.config.stripeSecretKey, hasSendgridKey: !!this.config.sendgridApiKey, hasSmtpConfig: !!(this.config.smtpHost && this.config.smtpPort), debug, }, version: '1.0.0', toolCount: 71, }; return { success: true, backup, backupId: `backup_${Date.now()}`, message: 'Configuration backup created successfully', }; }, }) ); // --- Test Connectivity --- this.server.register( defineTool({ name: 'test_connectivity', description: 'Test connectivity to external services', inputSchema: z.object({ service: z.enum(['pocketbase', 'stripe', 'sendgrid', 'all']).default('all'), }), async run(ctx: ToolContext) { const { service } = ctx.input; const { pocketbaseUrl, stripeSecretKey, sendgridApiKey } = this.config; const results: any = { timestamp: new Date().toISOString(), tests: {}, }; if (service === 'pocketbase' || service === 'all') { try { const start = Date.now(); const res = await fetch(`${pocketbaseUrl}/api/health`, { signal: AbortSignal.timeout(10000), }); results.tests.pocketbase = { success: res.ok, responseTime: Date.now() - start, statusCode: res.status, url: `${pocketbaseUrl}/api/health`, }; } catch (error) { results.tests.pocketbase = { success: false, error: (error as Error).message, url: `${pocketbaseUrl}/api/health`, }; } } if ((service === 'stripe' || service === 'all') && stripeSecretKey) { try { const start = Date.now(); const res = await fetch('https://api.stripe.com/v1/balance', { headers: { 'Authorization': `Bearer ${stripeSecretKey}` }, signal: AbortSignal.timeout(10000), }); results.tests.stripe = { success: res.ok, responseTime: Date.now() - start, statusCode: res.status, url: 'https://api.stripe.com/v1/balance', }; } catch (error) { results.tests.stripe = { success: false, error: (error as Error).message, url: 'https://api.stripe.com/v1/balance', }; } } if ((service === 'sendgrid' || service === 'all') && sendgridApiKey) { try { const start = Date.now(); const res = await fetch('https://api.sendgrid.com/v3/user/profile', { headers: { 'Authorization': `Bearer ${sendgridApiKey}` }, signal: AbortSignal.timeout(10000), }); results.tests.sendgrid = { success: res.ok, responseTime: Date.now() - start, statusCode: res.status, url: 'https://api.sendgrid.com/v3/user/profile', }; } catch (error) { results.tests.sendgrid = { success: false, error: (error as Error).message, url: 'https://api.sendgrid.com/v3/user/profile', }; } } const allTests = Object.values(results.tests); const successfulTests = allTests.filter((test: any) => test.success); results.summary = { total: allTests.length, successful: successfulTests.length, failed: allTests.length - successfulTests.length, overallSuccess: successfulTests.length === allTests.length, }; return results; }, }) ); // --- Utility: Generate UUID --- this.server.register( defineTool({ name: 'utility_generate_uuid', description: 'Generate a UUID v4', async run(ctx: ToolContext) { const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); return { uuid, timestamp: new Date().toISOString(), }; }, }) ); // --- Utility: Generate Random String --- this.server.register( defineTool({ name: 'utility_generate_random_string', description: 'Generate a random string', inputSchema: z.object({ length: z.number().min(1).max(256).default(32), includeNumbers: z.boolean().default(true), includeSymbols: z.boolean().default(false), includeUppercase: z.boolean().default(true), includeLowercase: z.boolean().default(true), }), async run(ctx: ToolContext) { const { length, includeNumbers, includeSymbols, includeUppercase, includeLowercase } = ctx.input; let chars = ''; if (includeLowercase) chars += 'abcdefghijklmnopqrstuvwxyz'; if (includeUppercase) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if (includeNumbers) chars += '0123456789'; if (includeSymbols) chars += '!@#$%^&*()_+-=[]{}|;:,.<>?'; if (chars === '') throw new Error('At least one character type must be included'); let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return { randomString: result, length, characterTypes: { includeNumbers, includeSymbols, includeUppercase, includeLowercase, }, timestamp: new Date().toISOString(), }; }, }) ); // --- Utility: Hash Text --- this.server.register( defineTool({ name: 'utility_hash_text', description: 'Hash text using various algorithms', inputSchema: z.object({ text: z.string(), algorithm: z.enum(['md5', 'sha1', 'sha256', 'sha512']).default('sha256'), }), async run(ctx: ToolContext) { const { text, algorithm } = ctx.input; // Simple hash implementation for demo let hash = ''; if (typeof crypto !== 'undefined' && crypto.subtle) { const encoder = new TextEncoder(); const data = encoder.encode(text); const hashBuffer = await crypto.subtle.digest(algorithm.toUpperCase().replace(/\d+/, '-$&'), data); const hashArray = Array.from(new Uint8Array(hashBuffer)); hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); } else { // Fallback simple hash for environments without crypto let hashCode = 0; for (let i = 0; i < text.length; i++) { const char = text.charCodeAt(i); hashCode = ((hashCode << 5) - hashCode) + char; hashCode = hashCode & hashCode; // Convert to 32bit integer } hash = Math.abs(hashCode).toString(16); } return { originalText: text, algorithm, hash, timestamp: new Date().toISOString(), }; }, }) ); // --- Utility: Encode/Decode Base64 --- this.server.register( defineTool({ name: 'utility_base64_encode', description: 'Encode text to Base64', inputSchema: z.object({ text: z.string() }), async run(ctx: ToolContext) { const { text } = ctx.input; const encoded = Buffer.from(text, 'utf8').toString('base64'); return { originalText: text, encodedText: encoded, timestamp: new Date().toISOString(), }; }, }) ); // --- Utility: Decode Base64 --- this.server.register( defineTool({ name: 'utility_base64_decode', description: 'Decode Base64 text', inputSchema: z.object({ encodedText: z.string() }), async run(ctx: ToolContext) { const { encodedText } = ctx.input; try { const decoded = Buffer.from(encodedText, 'base64').toString('utf8'); return { encodedText, decodedText: decoded, timestamp: new Date().toISOString(), }; } catch (error) { throw new Error('Invalid Base64 string'); } }, }) ); // --- Utility: URL Encode/Decode --- this.server.register( defineTool({ name: 'utility_url_encode', description: 'URL encode text', inputSchema: z.object({ text: z.string() }), async run(ctx: ToolContext) { const { text } = ctx.input; const encoded = encodeURIComponent(text); return { originalText: text, encodedText: encoded, timestamp: new Date().toISOString(), }; }, }) ); // --- Utility: URL Decode --- this.server.register( defineTool({ name: 'utility_url_decode', description: 'URL decode text', inputSchema: z.object({ encodedText: z.string() }), async run(ctx: ToolContext) { const { encodedText } = ctx.input; try { const decoded = decodeURIComponent(encodedText); return { encodedText, decodedText: decoded, timestamp: new Date().toISOString(), }; } catch (error) { throw new Error('Invalid URL encoded string'); } }, }) ); // --- Utility: JSON Validator --- this.server.register( defineTool({ name: 'utility_validate_json', description: 'Validate and format JSON', inputSchema: z.object({ jsonString: z.string() }), async run(ctx: ToolContext) { const { jsonString } = ctx.input; try { const parsed = JSON.parse(jsonString); const formatted = JSON.stringify(parsed, null, 2); return { originalJson: jsonString, isValid: true, formattedJson: formatted, parsedObject: parsed, timestamp: new Date().toISOString(), }; } catch (error) { return { originalJson: jsonString, isValid: false, error: (error as Error).message, timestamp: new Date().toISOString(), }; } }, }) ); // --- Utility: Password Generator --- this.server.register( defineTool({ name: 'utility_generate_password', description: 'Generate a secure password', inputSchema: z.object({ length: z.number().min(8).max(128).default(16), includeNumbers: z.boolean().default(true), includeSymbols: z.boolean().default(true), includeUppercase: z.boolean().default(true), includeLowercase: z.boolean().default(true), excludeSimilar: z.boolean().default(true), // Exclude 0, O, l, I, etc. }), async run(ctx: ToolContext) { const { length, includeNumbers, includeSymbols, includeUppercase, includeLowercase, excludeSimilar } = ctx.input; let chars = ''; if (includeLowercase) chars += excludeSimilar ? 'abcdefghijkmnopqrstuvwxyz' : 'abcdefghijklmnopqrstuvwxyz'; if (includeUppercase) chars += excludeSimilar ? 'ABCDEFGHJKLMNPQRSTUVWXYZ' : 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if (includeNumbers) chars += excludeSimilar ? '23456789' : '0123456789'; if (includeSymbols) chars += '!@#$%^&*()_+-=[]{}|;:,.<>?'; if (chars === '') throw new Error('At least one character type must be included'); let password = ''; for (let i = 0; i < length; i++) { password += chars.charAt(Math.floor(Math.random() * chars.length)); } // Calculate password strength let strength = 0; if (password.length >= 8) strength += 1; if (password.length >= 12) strength += 1; if (/[a-z]/.test(password)) strength += 1; if (/[A-Z]/.test(password)) strength += 1; if (/[0-9]/.test(password)) strength += 1; if (/[^A-Za-z0-9]/.test(password)) strength += 1; const strengthLevels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong', 'Very Strong']; return { password, length, strength: strengthLevels[Math.min(strength, 5)], strengthScore: strength, characterTypes: { includeNumbers, includeSymbols, includeUppercase, includeLowercase, excludeSimilar, }, timestamp: new Date().toISOString(), }; }, }) ); // --- Utility: QR Code Data Generator --- this.server.register( defineTool({ name: 'utility_generate_qr_data', description: 'Generate QR code data URL (simulation)', inputSchema: z.object({ text: z.string(), size: z.number().min(100).max(1000).default(200), }), async run(ctx: ToolContext) { const { text, size } = ctx.input; // In a real implementation, this would generate actual QR code const qrDataUrl = `data:image/svg+xml;base64,${Buffer.from(` <svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg"> <rect width="${size}" height="${size}" fill="white"/> <text x="50%" y="50%" text-anchor="middle" dy=".3em" font-family="monospace" font-size="12"> QR: ${text.substring(0, 20)}${text.length > 20 ? '...' : ''} </text> </svg> `).toString('base64')}`; return { originalText: text, qrCodeDataUrl: qrDataUrl, size, timestamp: new Date().toISOString(), }; }, }) ); } } // 5. Export function for Smithery export default function ({ config }: { config: z.infer<typeof configSchema> }) { const parseResult = configSchema.safeParse(config); if (!parseResult.success) { console.error('Invalid configuration:', parseResult.error); // Return a server with minimal tools for debugging const server = new MCPServer(); server.register( defineTool({ name: 'config_error', description: 'Configuration error details', run: async () => ({ error: 'Invalid configuration provided', details: parseResult.error.issues, expectedConfig: { pocketbaseUrl: 'https://your-pb-instance.com', adminEmail: 'admin@example.com', adminPassword: 'your-admin-password', stripeSecretKey: 'sk_...(optional)', sendgridApiKey: 'SG.xxx(optional)', }, }), }) ); return server; } const serverInstance = new ComprehensivePocketBaseMCPServer(); // Initialize synchronously with valid config serverInstance.config = parseResult.data; if (parseResult.data.debug) { console.log('MCP Server initialized with configuration:', { pocketbaseUrl: parseResult.data.pocketbaseUrl, hasStripeKey: !!parseResult.data.stripeSecretKey, hasSendgridKey: !!parseResult.data.sendgridApiKey, hasSmtpConfig: !!(parseResult.data.smtpHost && parseResult.data.smtpPort), }); } return serverInstance.server; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DynamicEndpoints/advanced-pocketbase-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server