This is page 2 of 3. Use http://codebase.md/jean-technologies/smartlead-mcp-server-local?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .env.example ├── .gitignore ├── DEVELOPER_ONBOARDING.md ├── Dockerfile ├── jest.config.js ├── llms-install.md ├── mcp_settings_example.json ├── package-lock.json ├── package.json ├── README.md ├── server │ └── license-server.js ├── smithery.yaml ├── src │ ├── cli.ts │ ├── config │ │ └── feature-config.ts │ ├── handlers │ │ ├── campaign.ts │ │ ├── clientManagement.ts │ │ ├── email.ts │ │ ├── lead.ts │ │ ├── smartDelivery.ts │ │ ├── smartSenders.ts │ │ ├── statistics.ts │ │ └── webhooks.ts │ ├── index.ts │ ├── licensing │ │ ├── index.ts │ │ └── stripe-integration.js │ ├── n8n │ │ └── index.ts │ ├── registry │ │ └── tool-registry.ts │ ├── supergateway-mock.ts │ ├── supergateway.ts │ ├── tools │ │ ├── campaign.ts │ │ ├── clientManagement.ts │ │ ├── email.d.ts │ │ ├── email.ts │ │ ├── lead.d.ts │ │ ├── lead.ts │ │ ├── smartDelivery.d.ts │ │ ├── smartDelivery.ts │ │ ├── smartSenders.ts │ │ ├── statistics.d.ts │ │ ├── statistics.ts │ │ └── webhooks.ts │ ├── types │ │ ├── campaign.ts │ │ ├── clientManagement.ts │ │ ├── common.ts │ │ ├── email.d.ts │ │ ├── email.ts │ │ ├── lead.ts │ │ ├── smartDelivery.ts │ │ ├── smartSenders.ts │ │ ├── statistics.d.ts │ │ ├── statistics.ts │ │ ├── supergateway.d.ts │ │ └── webhooks.ts │ └── utils │ └── download-tracker.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /src/handlers/lead.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { AxiosInstance } from 'axios'; 2 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; 3 | import { 4 | isListLeadsParams, 5 | isGetLeadParams, 6 | isAddLeadToCampaignParams, 7 | isUpdateLeadParams, 8 | isUpdateLeadStatusParams, 9 | isBulkImportLeadsParams, 10 | isDeleteLeadParams 11 | } from '../types/lead.js'; 12 | 13 | // Handler for lead-related tools 14 | export async function handleLeadTool( 15 | toolName: string, 16 | args: unknown, 17 | apiClient: AxiosInstance, 18 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 19 | ) { 20 | switch (toolName) { 21 | case 'smartlead_list_leads': { 22 | return handleListLeads(args, apiClient, withRetry); 23 | } 24 | case 'smartlead_get_lead': { 25 | return handleGetLead(args, apiClient, withRetry); 26 | } 27 | case 'smartlead_add_lead_to_campaign': { 28 | return handleAddLeadToCampaign(args, apiClient, withRetry); 29 | } 30 | case 'smartlead_update_lead': { 31 | return handleUpdateLead(args, apiClient, withRetry); 32 | } 33 | case 'smartlead_update_lead_status': { 34 | return handleUpdateLeadStatus(args, apiClient, withRetry); 35 | } 36 | case 'smartlead_bulk_import_leads': { 37 | return handleBulkImportLeads(args, apiClient, withRetry); 38 | } 39 | case 'smartlead_delete_lead': { 40 | return handleDeleteLead(args, apiClient, withRetry); 41 | } 42 | default: 43 | throw new Error(`Unknown lead tool: ${toolName}`); 44 | } 45 | } 46 | 47 | // Individual handlers for each tool 48 | async function handleListLeads( 49 | args: unknown, 50 | apiClient: AxiosInstance, 51 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 52 | ) { 53 | if (!isListLeadsParams(args)) { 54 | throw new McpError( 55 | ErrorCode.InvalidParams, 56 | 'Invalid arguments for smartlead_list_leads' 57 | ); 58 | } 59 | 60 | try { 61 | // Build query parameters from args 62 | const params = new URLSearchParams(); 63 | if (args.campaign_id !== undefined) { 64 | params.append('campaign_id', args.campaign_id.toString()); 65 | } 66 | if (args.status !== undefined) { 67 | params.append('status', args.status); 68 | } 69 | if (args.limit !== undefined) { 70 | params.append('limit', args.limit.toString()); 71 | } 72 | if (args.offset !== undefined) { 73 | params.append('offset', args.offset.toString()); 74 | } 75 | if (args.search !== undefined) { 76 | params.append('search', args.search); 77 | } 78 | if (args.start_date !== undefined) { 79 | params.append('start_date', args.start_date); 80 | } 81 | if (args.end_date !== undefined) { 82 | params.append('end_date', args.end_date); 83 | } 84 | 85 | const response = await withRetry( 86 | async () => apiClient.get('/leads', { params }), 87 | 'list leads' 88 | ); 89 | 90 | return { 91 | content: [ 92 | { 93 | type: 'text', 94 | text: JSON.stringify(response.data, null, 2), 95 | }, 96 | ], 97 | isError: false, 98 | }; 99 | } catch (error: any) { 100 | return { 101 | content: [{ 102 | type: 'text', 103 | text: `API Error: ${error.response?.data?.message || error.message}` 104 | }], 105 | isError: true, 106 | }; 107 | } 108 | } 109 | 110 | async function handleGetLead( 111 | args: unknown, 112 | apiClient: AxiosInstance, 113 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 114 | ) { 115 | if (!isGetLeadParams(args)) { 116 | throw new McpError( 117 | ErrorCode.InvalidParams, 118 | 'Invalid arguments for smartlead_get_lead' 119 | ); 120 | } 121 | 122 | try { 123 | const response = await withRetry( 124 | async () => apiClient.get(`/leads/${args.lead_id}`), 125 | 'get lead' 126 | ); 127 | 128 | return { 129 | content: [ 130 | { 131 | type: 'text', 132 | text: JSON.stringify(response.data, null, 2), 133 | }, 134 | ], 135 | isError: false, 136 | }; 137 | } catch (error: any) { 138 | return { 139 | content: [{ 140 | type: 'text', 141 | text: `API Error: ${error.response?.data?.message || error.message}` 142 | }], 143 | isError: true, 144 | }; 145 | } 146 | } 147 | 148 | async function handleAddLeadToCampaign( 149 | args: unknown, 150 | apiClient: AxiosInstance, 151 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 152 | ) { 153 | if (!isAddLeadToCampaignParams(args)) { 154 | throw new McpError( 155 | ErrorCode.InvalidParams, 156 | 'Invalid arguments for smartlead_add_lead_to_campaign' 157 | ); 158 | } 159 | 160 | try { 161 | const response = await withRetry( 162 | async () => apiClient.post(`/campaigns/${args.campaign_id}/leads`, args), 163 | 'add lead to campaign' 164 | ); 165 | 166 | return { 167 | content: [ 168 | { 169 | type: 'text', 170 | text: JSON.stringify(response.data, null, 2), 171 | }, 172 | ], 173 | isError: false, 174 | }; 175 | } catch (error: any) { 176 | return { 177 | content: [{ 178 | type: 'text', 179 | text: `API Error: ${error.response?.data?.message || error.message}` 180 | }], 181 | isError: true, 182 | }; 183 | } 184 | } 185 | 186 | async function handleUpdateLead( 187 | args: unknown, 188 | apiClient: AxiosInstance, 189 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 190 | ) { 191 | if (!isUpdateLeadParams(args)) { 192 | throw new McpError( 193 | ErrorCode.InvalidParams, 194 | 'Invalid arguments for smartlead_update_lead' 195 | ); 196 | } 197 | 198 | const { lead_id, ...leadData } = args; 199 | 200 | try { 201 | const response = await withRetry( 202 | async () => apiClient.put(`/leads/${lead_id}`, leadData), 203 | 'update lead' 204 | ); 205 | 206 | return { 207 | content: [ 208 | { 209 | type: 'text', 210 | text: JSON.stringify(response.data, null, 2), 211 | }, 212 | ], 213 | isError: false, 214 | }; 215 | } catch (error: any) { 216 | return { 217 | content: [{ 218 | type: 'text', 219 | text: `API Error: ${error.response?.data?.message || error.message}` 220 | }], 221 | isError: true, 222 | }; 223 | } 224 | } 225 | 226 | async function handleUpdateLeadStatus( 227 | args: unknown, 228 | apiClient: AxiosInstance, 229 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 230 | ) { 231 | if (!isUpdateLeadStatusParams(args)) { 232 | throw new McpError( 233 | ErrorCode.InvalidParams, 234 | 'Invalid arguments for smartlead_update_lead_status' 235 | ); 236 | } 237 | 238 | const { lead_id, status } = args; 239 | 240 | try { 241 | const response = await withRetry( 242 | async () => apiClient.put(`/leads/${lead_id}/status`, { status }), 243 | 'update lead status' 244 | ); 245 | 246 | return { 247 | content: [ 248 | { 249 | type: 'text', 250 | text: JSON.stringify(response.data, null, 2), 251 | }, 252 | ], 253 | isError: false, 254 | }; 255 | } catch (error: any) { 256 | return { 257 | content: [{ 258 | type: 'text', 259 | text: `API Error: ${error.response?.data?.message || error.message}` 260 | }], 261 | isError: true, 262 | }; 263 | } 264 | } 265 | 266 | async function handleBulkImportLeads( 267 | args: unknown, 268 | apiClient: AxiosInstance, 269 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 270 | ) { 271 | if (!isBulkImportLeadsParams(args)) { 272 | throw new McpError( 273 | ErrorCode.InvalidParams, 274 | 'Invalid arguments for smartlead_bulk_import_leads' 275 | ); 276 | } 277 | 278 | try { 279 | const response = await withRetry( 280 | async () => apiClient.post(`/campaigns/${args.campaign_id}/leads/bulk`, { 281 | leads: args.leads 282 | }), 283 | 'bulk import leads' 284 | ); 285 | 286 | return { 287 | content: [ 288 | { 289 | type: 'text', 290 | text: JSON.stringify(response.data, null, 2), 291 | }, 292 | ], 293 | isError: false, 294 | }; 295 | } catch (error: any) { 296 | return { 297 | content: [{ 298 | type: 'text', 299 | text: `API Error: ${error.response?.data?.message || error.message}` 300 | }], 301 | isError: true, 302 | }; 303 | } 304 | } 305 | 306 | async function handleDeleteLead( 307 | args: unknown, 308 | apiClient: AxiosInstance, 309 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 310 | ) { 311 | if (!isDeleteLeadParams(args)) { 312 | throw new McpError( 313 | ErrorCode.InvalidParams, 314 | 'Invalid arguments for smartlead_delete_lead' 315 | ); 316 | } 317 | 318 | try { 319 | const response = await withRetry( 320 | async () => apiClient.delete(`/leads/${args.lead_id}`), 321 | 'delete lead' 322 | ); 323 | 324 | return { 325 | content: [ 326 | { 327 | type: 'text', 328 | text: JSON.stringify(response.data, null, 2), 329 | }, 330 | ], 331 | isError: false, 332 | }; 333 | } catch (error: any) { 334 | return { 335 | content: [{ 336 | type: 'text', 337 | text: `API Error: ${error.response?.data?.message || error.message}` 338 | }], 339 | isError: true, 340 | }; 341 | } 342 | } ``` -------------------------------------------------------------------------------- /src/licensing/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import axios from 'axios'; 2 | import * as dotenv from 'dotenv'; 3 | 4 | // Ensure .env file is loaded 5 | dotenv.config(); 6 | 7 | // License levels 8 | export enum LicenseLevel { 9 | FREE = 'free', 10 | BASIC = 'basic', 11 | PREMIUM = 'premium' 12 | } 13 | 14 | // Feature definitions 15 | export interface LicenseFeatures { 16 | allowedCategories: string[]; 17 | maxRequests: number; 18 | n8nIntegration: boolean; 19 | smartleadApiAccess: boolean; 20 | } 21 | 22 | // Licensing configuration - This is just a fallback when offline 23 | const LICENSE_CONFIG: Record<LicenseLevel, LicenseFeatures> = { 24 | [LicenseLevel.FREE]: { 25 | allowedCategories: ['campaignManagement', 'leadManagement'], 26 | maxRequests: 100, 27 | n8nIntegration: false, 28 | smartleadApiAccess: true 29 | }, 30 | [LicenseLevel.BASIC]: { 31 | allowedCategories: ['campaignManagement', 'leadManagement', 'campaignStatistics', 'smartDelivery', 'webhooks', 'clientManagement', 'smartSenders'], 32 | maxRequests: 1000, 33 | n8nIntegration: true, 34 | smartleadApiAccess: true 35 | }, 36 | [LicenseLevel.PREMIUM]: { 37 | allowedCategories: ['campaignManagement', 'leadManagement', 'campaignStatistics', 'smartDelivery', 'webhooks', 'clientManagement', 'smartSenders'], 38 | maxRequests: 10000, 39 | n8nIntegration: true, 40 | smartleadApiAccess: true 41 | } 42 | }; 43 | 44 | // License validation result 45 | export interface LicenseValidationResult { 46 | valid: boolean; 47 | level: LicenseLevel; 48 | features: LicenseFeatures; 49 | message: string; 50 | usageCount: number; 51 | } 52 | 53 | // Generation info for server-side validation 54 | export interface FeatureRequestToken { 55 | token: string; 56 | expires: number; 57 | } 58 | 59 | // Validation status cache 60 | let cachedValidation: LicenseValidationResult | null = null; 61 | let cachedFeatureToken: FeatureRequestToken | null = null; 62 | const requestCounts: Record<string, number> = {}; 63 | 64 | // API configuration 65 | // Use the environment variable for the license server URL 66 | const LICENSE_SERVER_URL = process.env.LICENSE_SERVER_URL; 67 | const LICENSING_CACHE_TTL = 3600000; // 1 hour 68 | let lastValidationTime = 0; 69 | 70 | /** 71 | * Validate a license key 72 | * @param licenseKey Optional: license key to validate (primarily uses env var) 73 | * @returns License validation result 74 | */ 75 | export async function validateLicense(): Promise<LicenseValidationResult> { 76 | // Always return PREMIUM license regardless of key 77 | console.log('✅ License override: All features enabled in PREMIUM mode'); 78 | return createValidationResult( 79 | LicenseLevel.PREMIUM, 80 | true, 81 | 'License override enabled: All features available', 82 | 0 83 | ); 84 | 85 | // The following code will never be reached due to the early return above 86 | 87 | // Original code remains for reference but won't be executed: 88 | // Use the license key from the specific env var first 89 | // const apiKey = process.env.JEAN_LICENSE_KEY; 90 | // ... existing code ... 91 | } 92 | 93 | /** 94 | * Get a token for server-side feature validation 95 | * This adds security since critical operations will need server validation 96 | */ 97 | export async function getFeatureToken(): Promise<FeatureRequestToken | null> { 98 | // Ensure we have a valid license first 99 | const licenseResult = await validateLicense(); 100 | 101 | // Check if we have a valid cached token 102 | if (cachedFeatureToken && Date.now() < cachedFeatureToken.expires) { 103 | return cachedFeatureToken; 104 | } 105 | 106 | // If no license server URL or not in BASIC or PREMIUM tier, don't try to get a token 107 | if (!LICENSE_SERVER_URL || 108 | (licenseResult.level !== LicenseLevel.BASIC && 109 | licenseResult.level !== LicenseLevel.PREMIUM)) { 110 | return null; 111 | } 112 | 113 | // Try to get a fresh token from the server 114 | try { 115 | const apiKey = process.env.JEAN_LICENSE_KEY; 116 | if (!apiKey) return null; 117 | 118 | console.log(`Requesting feature token from ${LICENSE_SERVER_URL}/token`); 119 | const response = await axios.post(`${LICENSE_SERVER_URL}/token`, {}, { 120 | headers: { 121 | 'Authorization': `Bearer ${apiKey}`, 122 | 'Content-Type': 'application/json' 123 | }, 124 | timeout: 5000 125 | }); 126 | 127 | if (response.data && response.data.token) { 128 | cachedFeatureToken = { 129 | token: response.data.token, 130 | expires: response.data.expires || (Date.now() + 3600000) // Default to 1 hour if not specified 131 | }; 132 | 133 | console.log(`✅ Feature token acquired, valid until ${new Date(cachedFeatureToken.expires).toISOString()}`); 134 | return cachedFeatureToken; 135 | } 136 | 137 | return null; 138 | } catch (error: any) { 139 | console.error(`Failed to get feature token: ${error.message}`); 140 | // Silent failure - just return null 141 | return null; 142 | } 143 | } 144 | 145 | /** 146 | * Track usage for a license 147 | * @param licenseKey The license key 148 | * @param toolName The tool being used 149 | */ 150 | export async function trackUsage(licenseKey?: string, toolName = 'unknown'): Promise<void> { 151 | const apiKey = licenseKey || process.env.JEAN_LICENSE_KEY; 152 | if (!apiKey || !LICENSE_SERVER_URL) return; 153 | 154 | // Get a unique identifier for tracking 155 | const machineId = getMachineId(); 156 | 157 | // Increment local counter 158 | const key = apiKey.substring(0, 8); // Use part of the key as an identifier 159 | requestCounts[key] = (requestCounts[key] || 0) + 1; 160 | 161 | // Only report every 10 requests to reduce API load 162 | if (requestCounts[key] % 10 !== 0) return; 163 | 164 | try { 165 | // Report usage asynchronously (don't await) 166 | axios.post(`${LICENSE_SERVER_URL}/track`, { 167 | key: apiKey, 168 | tool: toolName, 169 | count: 10, // Batch reporting 170 | machineId 171 | }, { 172 | headers: { 173 | 'Authorization': `Bearer ${apiKey}`, 174 | 'Content-Type': 'application/json' 175 | } 176 | }).catch((error) => { 177 | // Silently fail with more context - we don't want to impact performance 178 | console.debug(`Usage tracking failed: ${error.message}`); 179 | }); 180 | } catch (error) { 181 | // Ignore errors - usage tracking is non-critical 182 | } 183 | } 184 | 185 | /** 186 | * Create a validation result object 187 | */ 188 | function createValidationResult( 189 | level: LicenseLevel, 190 | valid: boolean, 191 | message: string, 192 | usageCount = 0 193 | ): LicenseValidationResult { 194 | return { 195 | valid, 196 | level, 197 | features: LICENSE_CONFIG[level], 198 | message, 199 | usageCount 200 | }; 201 | } 202 | 203 | /** 204 | * Check if a feature is available in the current license 205 | * @param feature The feature to check 206 | * @returns Whether the feature is available 207 | */ 208 | export async function isFeatureEnabled(feature: keyof LicenseFeatures): Promise<boolean> { 209 | // Always return true for all features 210 | return true; 211 | } 212 | 213 | /** 214 | * Check if a category is available in the current license 215 | * @param category The category to check 216 | * @returns Whether the category is available 217 | */ 218 | export async function isCategoryEnabled(category: string): Promise<boolean> { 219 | // Always return true for all categories 220 | return true; 221 | } 222 | 223 | /** 224 | * Get the current license information 225 | * @returns Current license information 226 | */ 227 | export async function getLicenseInfo(): Promise<LicenseValidationResult> { 228 | return validateLicense(); 229 | } 230 | 231 | /** 232 | * Get a unique identifier for the machine 233 | * This helps prevent sharing of license keys 234 | */ 235 | function getMachineId(): string { 236 | try { 237 | // Use environment variables to create a semi-unique ID 238 | // This is not perfect but provides basic machine identification 239 | const os = process.platform; 240 | const cpus = process.env.NUMBER_OF_PROCESSORS || ''; 241 | const username = process.env.USER || process.env.USERNAME || ''; 242 | const hostname = process.env.HOSTNAME || ''; 243 | 244 | // Create a simple hash of these values 245 | const combinedString = `${os}-${cpus}-${username}-${hostname}`; 246 | let hash = 0; 247 | for (let i = 0; i < combinedString.length; i++) { 248 | hash = ((hash << 5) - hash) + combinedString.charCodeAt(i); 249 | hash |= 0; // Convert to 32bit integer 250 | } 251 | 252 | return Math.abs(hash).toString(16); 253 | } catch (e) { 254 | // Fallback to a random ID if we can't get system info 255 | return Math.random().toString(36).substring(2, 15); 256 | } 257 | } 258 | 259 | /** 260 | * Print a summary of the current license status to the console 261 | * This is useful for displaying on server startup 262 | */ 263 | export async function printLicenseStatus(): Promise<void> { 264 | try { 265 | const validation = await validateLicense(); 266 | 267 | console.log('\n========== LICENSE STATUS =========='); 268 | console.log(`License Tier: ${validation.level.toUpperCase()}`); 269 | console.log(`Valid: ${validation.valid ? 'Yes' : 'No'}`); 270 | console.log(`Message: ${validation.message}`); 271 | console.log('Available Features:'); 272 | console.log(`- Categories: ${validation.features.allowedCategories.join(', ')}`); 273 | console.log(`- Max Requests: ${validation.features.maxRequests}`); 274 | console.log(`- n8n Integration: ${validation.features.n8nIntegration ? 'Enabled' : 'Disabled'}`); 275 | console.log(`- Smartlead API Access: ${validation.features.smartleadApiAccess ? 'Enabled' : 'Disabled'}`); 276 | 277 | if (!validation.valid) { 278 | console.log('\n⚠️ ATTENTION ⚠️'); 279 | console.log('Your license is not valid or is running in limited mode.'); 280 | if (!process.env.JEAN_LICENSE_KEY) { 281 | console.log('You have not set a license key (JEAN_LICENSE_KEY) in your .env file.'); 282 | } 283 | if (!process.env.LICENSE_SERVER_URL) { 284 | console.log('The LICENSE_SERVER_URL is not configured in your .env file.'); 285 | } 286 | console.log('To enable all features, please check your configuration.'); 287 | } 288 | 289 | if (process.env.LICENSE_LEVEL_OVERRIDE) { 290 | console.log('\n⚠️ DEVELOPMENT MODE ⚠️'); 291 | console.log(`License level is overridden to: ${process.env.LICENSE_LEVEL_OVERRIDE.toUpperCase()}`); 292 | console.log('This override should not be used in production environments.'); 293 | } 294 | 295 | console.log('=====================================\n'); 296 | } catch (error) { 297 | console.error('Failed to print license status:', error); 298 | } 299 | } ``` -------------------------------------------------------------------------------- /server/license-server.js: -------------------------------------------------------------------------------- ```javascript 1 | import express from 'express'; 2 | import crypto from 'crypto'; 3 | import { createClient } from '@supabase/supabase-js'; 4 | import Stripe from 'stripe'; 5 | import dotenv from 'dotenv'; 6 | 7 | dotenv.config(); 8 | 9 | // Initialize Express app 10 | const app = express(); 11 | app.use(express.json()); 12 | 13 | // Initialize Stripe 14 | const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); 15 | 16 | // Initialize Supabase client 17 | const supabase = createClient( 18 | process.env.SUPABASE_URL, 19 | process.env.SUPABASE_SERVICE_KEY 20 | ); 21 | 22 | // License levels 23 | const LicenseLevel = { 24 | FREE: 'free', 25 | BASIC: 'basic', 26 | PREMIUM: 'premium' 27 | }; 28 | 29 | // Feature mappings 30 | const LicenseFeatures = { 31 | [LicenseLevel.FREE]: { 32 | allowedCategories: ['campaignManagement', 'leadManagement'], 33 | maxRequests: 100, 34 | n8nIntegration: false, 35 | smartleadApiAccess: true 36 | }, 37 | [LicenseLevel.BASIC]: { 38 | allowedCategories: ['campaignManagement', 'leadManagement', 'campaignStatistics', 'smartDelivery'], 39 | maxRequests: 1000, 40 | n8nIntegration: false, 41 | smartleadApiAccess: true 42 | }, 43 | [LicenseLevel.PREMIUM]: { 44 | allowedCategories: ['campaignManagement', 'leadManagement', 'campaignStatistics', 'smartDelivery', 'webhooks', 'clientManagement', 'smartSenders'], 45 | maxRequests: 10000, 46 | n8nIntegration: true, 47 | smartleadApiAccess: true 48 | } 49 | }; 50 | 51 | // Generate a secure license key 52 | function generateLicenseKey() { 53 | return crypto.randomBytes(24).toString('hex'); 54 | } 55 | 56 | // Generate a temporary feature token 57 | function generateFeatureToken() { 58 | return { 59 | token: crypto.randomBytes(32).toString('hex'), 60 | expires: Date.now() + 3600000 // 1 hour 61 | }; 62 | } 63 | 64 | // API Routes 65 | 66 | // Validate a license key 67 | app.post('/validate', async (req, res) => { 68 | try { 69 | const { Authorization } = req.headers; 70 | const apiKey = Authorization ? Authorization.replace('Bearer ', '') : null; 71 | const machineId = req.headers['x-client-id']; 72 | 73 | if (!apiKey) { 74 | return res.json({ 75 | valid: false, 76 | level: LicenseLevel.FREE, 77 | message: 'No API key provided' 78 | }); 79 | } 80 | 81 | // Query license in Supabase 82 | const { data, error } = await supabase 83 | .from('licenses') 84 | .select('*') 85 | .eq('key', apiKey) 86 | .single(); 87 | 88 | if (error || !data) { 89 | return res.json({ 90 | valid: false, 91 | level: LicenseLevel.FREE, 92 | message: 'Invalid license key' 93 | }); 94 | } 95 | 96 | // Check if license is expired 97 | if (data.expires && new Date(data.expires) < new Date()) { 98 | return res.json({ 99 | valid: false, 100 | level: LicenseLevel.FREE, 101 | message: 'License expired' 102 | }); 103 | } 104 | 105 | // Check if this machine is authorized 106 | if (data.machine_ids && data.machine_ids.length > 0) { 107 | if (!machineId || !data.machine_ids.includes(machineId)) { 108 | // If this is a new machine, check if we've reached the limit 109 | if (data.machine_ids.length >= data.max_machines) { 110 | return res.json({ 111 | valid: false, 112 | level: LicenseLevel.FREE, 113 | message: 'Maximum number of machines reached' 114 | }); 115 | } 116 | 117 | // Otherwise add this machine to the authorized list 118 | const updatedMachines = [...data.machine_ids, machineId]; 119 | await supabase 120 | .from('licenses') 121 | .update({ machine_ids: updatedMachines }) 122 | .eq('id', data.id); 123 | } 124 | } else { 125 | // Initialize the machine_ids array with this machine 126 | await supabase 127 | .from('licenses') 128 | .update({ machine_ids: [machineId] }) 129 | .eq('id', data.id); 130 | } 131 | 132 | // Generate a feature token for server-side validation 133 | const featureToken = generateFeatureToken(); 134 | 135 | // Track usage 136 | await supabase 137 | .from('license_usage') 138 | .insert({ 139 | license_id: data.id, 140 | machine_id: machineId, 141 | timestamp: new Date().toISOString() 142 | }); 143 | 144 | // Increment usage count 145 | const { count } = await supabase 146 | .from('license_usage') 147 | .select('count', { count: 'exact' }) 148 | .eq('license_id', data.id); 149 | 150 | return res.json({ 151 | valid: true, 152 | level: data.level, 153 | usage: count || 0, 154 | featureToken: featureToken.token, 155 | tokenExpires: 3600, // 1 hour in seconds 156 | message: 'License validated successfully' 157 | }); 158 | } catch (error) { 159 | console.error('License validation error:', error); 160 | return res.status(500).json({ 161 | valid: false, 162 | level: LicenseLevel.FREE, 163 | message: 'Server error during validation' 164 | }); 165 | } 166 | }); 167 | 168 | // Validate an installation 169 | app.post('/validate-install', async (req, res) => { 170 | try { 171 | const { apiKey, machineId, version, installPath } = req.body; 172 | 173 | if (!apiKey) { 174 | return res.json({ 175 | success: false, 176 | message: 'No API key provided' 177 | }); 178 | } 179 | 180 | // Query license in Supabase 181 | const { data, error } = await supabase 182 | .from('licenses') 183 | .select('*') 184 | .eq('key', apiKey) 185 | .single(); 186 | 187 | if (error || !data) { 188 | return res.json({ 189 | success: false, 190 | message: 'Invalid license key' 191 | }); 192 | } 193 | 194 | // Check if license is expired 195 | if (data.expires && new Date(data.expires) < new Date()) { 196 | return res.json({ 197 | success: false, 198 | message: 'License expired' 199 | }); 200 | } 201 | 202 | // Record installation info 203 | await supabase 204 | .from('installations') 205 | .insert({ 206 | license_id: data.id, 207 | machine_id: machineId, 208 | version, 209 | install_path: installPath, 210 | installed_at: new Date().toISOString() 211 | }); 212 | 213 | // Return license details 214 | return res.json({ 215 | success: true, 216 | level: data.level, 217 | features: LicenseFeatures[data.level].allowedCategories, 218 | expires: data.expires, 219 | message: 'Installation validated successfully' 220 | }); 221 | } catch (error) { 222 | console.error('Installation validation error:', error); 223 | return res.status(500).json({ 224 | success: false, 225 | message: 'Server error during installation validation' 226 | }); 227 | } 228 | }); 229 | 230 | // Generate a feature token (used for server-side validation) 231 | app.post('/token', async (req, res) => { 232 | try { 233 | const { Authorization } = req.headers; 234 | const apiKey = Authorization ? Authorization.replace('Bearer ', '') : null; 235 | 236 | if (!apiKey) { 237 | return res.status(401).json({ error: 'Unauthorized' }); 238 | } 239 | 240 | // Verify the license is valid 241 | const { data, error } = await supabase 242 | .from('licenses') 243 | .select('level') 244 | .eq('key', apiKey) 245 | .single(); 246 | 247 | if (error || !data) { 248 | return res.status(401).json({ error: 'Invalid license' }); 249 | } 250 | 251 | // Generate a new token 252 | const featureToken = generateFeatureToken(); 253 | 254 | // Store the token in Supabase with expiration 255 | await supabase 256 | .from('feature_tokens') 257 | .insert({ 258 | token: featureToken.token, 259 | license_key: apiKey, 260 | expires_at: new Date(featureToken.expires).toISOString() 261 | }); 262 | 263 | return res.json({ 264 | token: featureToken.token, 265 | expires: featureToken.expires 266 | }); 267 | } catch (error) { 268 | console.error('Token generation error:', error); 269 | return res.status(500).json({ error: 'Server error' }); 270 | } 271 | }); 272 | 273 | // Handle Stripe webhook 274 | app.post('/webhook', async (req, res) => { 275 | const sig = req.headers['stripe-signature']; 276 | 277 | try { 278 | const event = stripe.webhooks.constructEvent( 279 | req.body, 280 | sig, 281 | process.env.STRIPE_WEBHOOK_SECRET 282 | ); 283 | 284 | switch (event.type) { 285 | case 'customer.subscription.created': 286 | case 'customer.subscription.updated': { 287 | const subscription = event.data.object; 288 | const customerId = subscription.customer; 289 | 290 | // Get the product details to determine license level 291 | const product = await stripe.products.retrieve( 292 | subscription.items.data[0].plan.product 293 | ); 294 | 295 | // Get license level from product metadata 296 | const level = product.metadata.license_level || LicenseLevel.BASIC; 297 | 298 | // Generate a license key 299 | const licenseKey = generateLicenseKey(); 300 | 301 | // Get the customer email 302 | const customer = await stripe.customers.retrieve(customerId); 303 | 304 | // Store the license in Supabase 305 | await supabase 306 | .from('licenses') 307 | .upsert({ 308 | key: licenseKey, 309 | customer_id: customerId, 310 | customer_email: customer.email, 311 | level, 312 | created_at: new Date().toISOString(), 313 | expires: subscription.current_period_end 314 | ? new Date(subscription.current_period_end * 1000).toISOString() 315 | : null, 316 | max_machines: level === LicenseLevel.PREMIUM ? 5 : 2, // Limit based on tier 317 | machine_ids: [], 318 | active: true 319 | }, { onConflict: 'customer_id' }); 320 | 321 | // Update customer metadata with license key 322 | await stripe.customers.update(customerId, { 323 | metadata: { 324 | license_key: licenseKey, 325 | license_level: level 326 | } 327 | }); 328 | 329 | break; 330 | } 331 | 332 | case 'customer.subscription.deleted': { 333 | const subscription = event.data.object; 334 | const customerId = subscription.customer; 335 | 336 | // Find the license by customer ID 337 | const { data } = await supabase 338 | .from('licenses') 339 | .select('id') 340 | .eq('customer_id', customerId) 341 | .single(); 342 | 343 | if (data) { 344 | // Downgrade the license to free tier 345 | await supabase 346 | .from('licenses') 347 | .update({ 348 | level: LicenseLevel.FREE, 349 | expires: new Date().toISOString() // Expire now 350 | }) 351 | .eq('id', data.id); 352 | } 353 | 354 | break; 355 | } 356 | } 357 | 358 | res.json({ received: true }); 359 | } catch (error) { 360 | console.error('Webhook error:', error); 361 | res.status(400).json({ error: error.message }); 362 | } 363 | }); 364 | 365 | // Start server 366 | const PORT = process.env.PORT || 3000; 367 | app.listen(PORT, () => { 368 | console.log(`License server running on port ${PORT}`); 369 | }); ``` -------------------------------------------------------------------------------- /src/tools/email.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { CategoryTool, ToolCategory } from '../types/common.js'; 2 | 3 | // Email Account Management Tools 4 | export const LIST_EMAIL_ACCOUNTS_CAMPAIGN_TOOL: CategoryTool = { 5 | name: 'smartlead_list_email_accounts_campaign', 6 | description: 'List all email accounts associated with a specific campaign.', 7 | category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, 8 | inputSchema: { 9 | type: 'object', 10 | properties: { 11 | campaign_id: { 12 | type: 'number', 13 | description: 'ID of the campaign to get email accounts for', 14 | }, 15 | status: { 16 | type: 'string', 17 | enum: ['active', 'disconnected', 'pending'], 18 | description: 'Filter email accounts by status', 19 | }, 20 | limit: { 21 | type: 'number', 22 | description: 'Maximum number of email accounts to return', 23 | }, 24 | offset: { 25 | type: 'number', 26 | description: 'Offset for pagination', 27 | }, 28 | }, 29 | required: ['campaign_id'], 30 | }, 31 | }; 32 | 33 | export const ADD_EMAIL_TO_CAMPAIGN_TOOL: CategoryTool = { 34 | name: 'smartlead_add_email_to_campaign', 35 | description: 'Add an email account to a campaign.', 36 | category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, 37 | inputSchema: { 38 | type: 'object', 39 | properties: { 40 | campaign_id: { 41 | type: 'number', 42 | description: 'ID of the campaign to add the email account to', 43 | }, 44 | email_account_id: { 45 | type: 'number', 46 | description: 'ID of the email account to add to the campaign', 47 | }, 48 | }, 49 | required: ['campaign_id', 'email_account_id'], 50 | }, 51 | }; 52 | 53 | export const REMOVE_EMAIL_FROM_CAMPAIGN_TOOL: CategoryTool = { 54 | name: 'smartlead_remove_email_from_campaign', 55 | description: 'Remove an email account from a campaign.', 56 | category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, 57 | inputSchema: { 58 | type: 'object', 59 | properties: { 60 | campaign_id: { 61 | type: 'number', 62 | description: 'ID of the campaign to remove the email account from', 63 | }, 64 | email_account_id: { 65 | type: 'number', 66 | description: 'ID of the email account to remove from the campaign', 67 | }, 68 | }, 69 | required: ['campaign_id', 'email_account_id'], 70 | }, 71 | }; 72 | 73 | export const FETCH_EMAIL_ACCOUNTS_TOOL: CategoryTool = { 74 | name: 'smartlead_fetch_email_accounts', 75 | description: 'Fetch all email accounts associated with the user.', 76 | category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, 77 | inputSchema: { 78 | type: 'object', 79 | properties: { 80 | status: { 81 | type: 'string', 82 | enum: ['active', 'disconnected', 'pending'], 83 | description: 'Filter email accounts by status', 84 | }, 85 | limit: { 86 | type: 'number', 87 | description: 'Maximum number of email accounts to return', 88 | }, 89 | offset: { 90 | type: 'number', 91 | description: 'Offset for pagination', 92 | }, 93 | }, 94 | }, 95 | }; 96 | 97 | export const CREATE_EMAIL_ACCOUNT_TOOL: CategoryTool = { 98 | name: 'smartlead_create_email_account', 99 | description: 'Create a new email account.', 100 | category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, 101 | inputSchema: { 102 | type: 'object', 103 | properties: { 104 | email: { 105 | type: 'string', 106 | description: 'Email address', 107 | }, 108 | provider: { 109 | type: 'string', 110 | description: 'Email provider (e.g., "gmail", "outlook", "custom")', 111 | }, 112 | name: { 113 | type: 'string', 114 | description: 'Display name for the email account', 115 | }, 116 | smtp_host: { 117 | type: 'string', 118 | description: 'SMTP server hostname (for custom providers)', 119 | }, 120 | smtp_port: { 121 | type: 'number', 122 | description: 'SMTP server port (for custom providers)', 123 | }, 124 | smtp_username: { 125 | type: 'string', 126 | description: 'SMTP username (for custom providers)', 127 | }, 128 | smtp_password: { 129 | type: 'string', 130 | description: 'SMTP password (for custom providers)', 131 | }, 132 | imap_host: { 133 | type: 'string', 134 | description: 'IMAP server hostname (for custom providers)', 135 | }, 136 | imap_port: { 137 | type: 'number', 138 | description: 'IMAP server port (for custom providers)', 139 | }, 140 | imap_username: { 141 | type: 'string', 142 | description: 'IMAP username (for custom providers)', 143 | }, 144 | imap_password: { 145 | type: 'string', 146 | description: 'IMAP password (for custom providers)', 147 | }, 148 | oauth_token: { 149 | type: 'string', 150 | description: 'OAuth token (for OAuth-based providers)', 151 | }, 152 | tags: { 153 | type: 'array', 154 | items: { 155 | type: 'string', 156 | }, 157 | description: 'Tags to assign to the email account', 158 | }, 159 | }, 160 | required: ['email', 'provider'], 161 | }, 162 | }; 163 | 164 | export const UPDATE_EMAIL_ACCOUNT_TOOL: CategoryTool = { 165 | name: 'smartlead_update_email_account', 166 | description: 'Update an existing email account.', 167 | category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, 168 | inputSchema: { 169 | type: 'object', 170 | properties: { 171 | email_account_id: { 172 | type: 'number', 173 | description: 'ID of the email account to update', 174 | }, 175 | name: { 176 | type: 'string', 177 | description: 'Display name for the email account', 178 | }, 179 | smtp_host: { 180 | type: 'string', 181 | description: 'SMTP server hostname', 182 | }, 183 | smtp_port: { 184 | type: 'number', 185 | description: 'SMTP server port', 186 | }, 187 | smtp_username: { 188 | type: 'string', 189 | description: 'SMTP username', 190 | }, 191 | smtp_password: { 192 | type: 'string', 193 | description: 'SMTP password', 194 | }, 195 | imap_host: { 196 | type: 'string', 197 | description: 'IMAP server hostname', 198 | }, 199 | imap_port: { 200 | type: 'number', 201 | description: 'IMAP server port', 202 | }, 203 | imap_username: { 204 | type: 'string', 205 | description: 'IMAP username', 206 | }, 207 | imap_password: { 208 | type: 'string', 209 | description: 'IMAP password', 210 | }, 211 | oauth_token: { 212 | type: 'string', 213 | description: 'OAuth token', 214 | }, 215 | status: { 216 | type: 'string', 217 | enum: ['active', 'paused', 'disconnected'], 218 | description: 'Status of the email account', 219 | }, 220 | }, 221 | required: ['email_account_id'], 222 | }, 223 | }; 224 | 225 | export const FETCH_EMAIL_ACCOUNT_BY_ID_TOOL: CategoryTool = { 226 | name: 'smartlead_fetch_email_account_by_id', 227 | description: 'Fetch a specific email account by ID.', 228 | category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, 229 | inputSchema: { 230 | type: 'object', 231 | properties: { 232 | email_account_id: { 233 | type: 'number', 234 | description: 'ID of the email account to fetch', 235 | }, 236 | }, 237 | required: ['email_account_id'], 238 | }, 239 | }; 240 | 241 | export const UPDATE_EMAIL_WARMUP_TOOL: CategoryTool = { 242 | name: 'smartlead_update_email_warmup', 243 | description: 'Add or update warmup settings for an email account.', 244 | category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, 245 | inputSchema: { 246 | type: 'object', 247 | properties: { 248 | email_account_id: { 249 | type: 'number', 250 | description: 'ID of the email account to update warmup settings for', 251 | }, 252 | enabled: { 253 | type: 'boolean', 254 | description: 'Whether warmup is enabled for this email account', 255 | }, 256 | daily_limit: { 257 | type: 'number', 258 | description: 'Daily limit for warmup emails', 259 | }, 260 | warmup_settings: { 261 | type: 'object', 262 | properties: { 263 | start_time: { 264 | type: 'string', 265 | description: 'Start time for warmup in HH:MM format', 266 | }, 267 | end_time: { 268 | type: 'string', 269 | description: 'End time for warmup in HH:MM format', 270 | }, 271 | days_of_week: { 272 | type: 'array', 273 | items: { 274 | type: 'number', 275 | }, 276 | description: 'Days of the week for warmup (1-7, where 1 is Monday)', 277 | }, 278 | }, 279 | description: 'Additional warmup settings', 280 | }, 281 | }, 282 | required: ['email_account_id', 'enabled'], 283 | }, 284 | }; 285 | 286 | export const RECONNECT_EMAIL_ACCOUNT_TOOL: CategoryTool = { 287 | name: 'smartlead_reconnect_email_account', 288 | description: 'Reconnect a failed email account.', 289 | category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, 290 | inputSchema: { 291 | type: 'object', 292 | properties: { 293 | email_account_id: { 294 | type: 'number', 295 | description: 'ID of the email account to reconnect', 296 | }, 297 | connection_details: { 298 | type: 'object', 299 | properties: { 300 | smtp_host: { 301 | type: 'string', 302 | description: 'SMTP server hostname', 303 | }, 304 | smtp_port: { 305 | type: 'number', 306 | description: 'SMTP server port', 307 | }, 308 | smtp_username: { 309 | type: 'string', 310 | description: 'SMTP username', 311 | }, 312 | smtp_password: { 313 | type: 'string', 314 | description: 'SMTP password', 315 | }, 316 | imap_host: { 317 | type: 'string', 318 | description: 'IMAP server hostname', 319 | }, 320 | imap_port: { 321 | type: 'number', 322 | description: 'IMAP server port', 323 | }, 324 | imap_username: { 325 | type: 'string', 326 | description: 'IMAP username', 327 | }, 328 | imap_password: { 329 | type: 'string', 330 | description: 'IMAP password', 331 | }, 332 | oauth_token: { 333 | type: 'string', 334 | description: 'OAuth token', 335 | }, 336 | }, 337 | description: 'Connection details for reconnecting the email account', 338 | }, 339 | }, 340 | required: ['email_account_id'], 341 | }, 342 | }; 343 | 344 | export const UPDATE_EMAIL_ACCOUNT_TAG_TOOL: CategoryTool = { 345 | name: 'smartlead_update_email_account_tag', 346 | description: 'Update tags for an email account.', 347 | category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, 348 | inputSchema: { 349 | type: 'object', 350 | properties: { 351 | email_account_id: { 352 | type: 'number', 353 | description: 'ID of the email account to update tags for', 354 | }, 355 | tags: { 356 | type: 'array', 357 | items: { 358 | type: 'string', 359 | }, 360 | description: 'Tags to assign to the email account', 361 | }, 362 | }, 363 | required: ['email_account_id', 'tags'], 364 | }, 365 | }; 366 | 367 | // Export all email tools as an array 368 | export const emailTools = [ 369 | LIST_EMAIL_ACCOUNTS_CAMPAIGN_TOOL, 370 | ADD_EMAIL_TO_CAMPAIGN_TOOL, 371 | REMOVE_EMAIL_FROM_CAMPAIGN_TOOL, 372 | FETCH_EMAIL_ACCOUNTS_TOOL, 373 | CREATE_EMAIL_ACCOUNT_TOOL, 374 | UPDATE_EMAIL_ACCOUNT_TOOL, 375 | FETCH_EMAIL_ACCOUNT_BY_ID_TOOL, 376 | UPDATE_EMAIL_WARMUP_TOOL, 377 | RECONNECT_EMAIL_ACCOUNT_TOOL, 378 | UPDATE_EMAIL_ACCOUNT_TAG_TOOL, 379 | ]; ``` -------------------------------------------------------------------------------- /src/tools/campaign.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { CategoryTool, ToolCategory } from '../types/common.js'; 2 | 3 | // Campaign Management Tools 4 | export const CREATE_CAMPAIGN_TOOL: CategoryTool = { 5 | name: 'smartlead_create_campaign', 6 | description: 'Create a new campaign in Smartlead.', 7 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 8 | inputSchema: { 9 | type: 'object', 10 | properties: { 11 | name: { 12 | type: 'string', 13 | description: 'Name of the campaign', 14 | }, 15 | client_id: { 16 | type: 'number', 17 | description: 'Client ID for the campaign', 18 | }, 19 | }, 20 | required: ['name'], 21 | }, 22 | }; 23 | 24 | export const UPDATE_CAMPAIGN_SCHEDULE_TOOL: CategoryTool = { 25 | name: 'smartlead_update_campaign_schedule', 26 | description: 'Update a campaign\'s schedule settings.', 27 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 28 | inputSchema: { 29 | type: 'object', 30 | properties: { 31 | campaign_id: { 32 | type: 'number', 33 | description: 'ID of the campaign to update', 34 | }, 35 | timezone: { 36 | type: 'string', 37 | description: 'Timezone for the campaign (e.g., "America/Los_Angeles")', 38 | }, 39 | days_of_the_week: { 40 | type: 'array', 41 | items: { type: 'number' }, 42 | description: 'Days of the week to send emails (1-7, where 1 is Monday)', 43 | }, 44 | start_hour: { 45 | type: 'string', 46 | description: 'Start hour in 24-hour format (e.g., "09:00")', 47 | }, 48 | end_hour: { 49 | type: 'string', 50 | description: 'End hour in 24-hour format (e.g., "17:00")', 51 | }, 52 | min_time_btw_emails: { 53 | type: 'number', 54 | description: 'Minimum time between emails in minutes', 55 | }, 56 | max_new_leads_per_day: { 57 | type: 'number', 58 | description: 'Maximum number of new leads per day', 59 | }, 60 | schedule_start_time: { 61 | type: 'string', 62 | description: 'Schedule start time in ISO format', 63 | }, 64 | }, 65 | required: ['campaign_id'], 66 | }, 67 | }; 68 | 69 | export const UPDATE_CAMPAIGN_SETTINGS_TOOL: CategoryTool = { 70 | name: 'smartlead_update_campaign_settings', 71 | description: 'Update a campaign\'s general settings.', 72 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 73 | inputSchema: { 74 | type: 'object', 75 | properties: { 76 | campaign_id: { 77 | type: 'number', 78 | description: 'ID of the campaign to update', 79 | }, 80 | name: { 81 | type: 'string', 82 | description: 'New name for the campaign', 83 | }, 84 | status: { 85 | type: 'string', 86 | enum: ['active', 'paused', 'completed'], 87 | description: 'Status of the campaign', 88 | }, 89 | settings: { 90 | type: 'object', 91 | description: 'Additional campaign settings', 92 | }, 93 | }, 94 | required: ['campaign_id'], 95 | }, 96 | }; 97 | 98 | export const UPDATE_CAMPAIGN_STATUS_TOOL: CategoryTool = { 99 | name: 'smartlead_update_campaign_status', 100 | description: 'Update the status of a campaign. Use this specifically for changing a campaign\'s status.', 101 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 102 | inputSchema: { 103 | type: 'object', 104 | properties: { 105 | campaign_id: { 106 | type: 'number', 107 | description: 'ID of the campaign to update the status for', 108 | }, 109 | status: { 110 | type: 'string', 111 | enum: ['PAUSED', 'STOPPED', 'START'], 112 | description: 'New status for the campaign (must be in uppercase)', 113 | }, 114 | }, 115 | required: ['campaign_id', 'status'], 116 | }, 117 | }; 118 | 119 | export const GET_CAMPAIGN_TOOL: CategoryTool = { 120 | name: 'smartlead_get_campaign', 121 | description: 'Get details of a specific campaign by ID.', 122 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 123 | inputSchema: { 124 | type: 'object', 125 | properties: { 126 | campaign_id: { 127 | type: 'number', 128 | description: 'ID of the campaign to retrieve', 129 | }, 130 | }, 131 | required: ['campaign_id'], 132 | }, 133 | }; 134 | 135 | export const LIST_CAMPAIGNS_TOOL: CategoryTool = { 136 | name: 'smartlead_list_campaigns', 137 | description: 'List all campaigns with optional filtering.', 138 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 139 | inputSchema: { 140 | type: 'object', 141 | properties: { 142 | status: { 143 | type: 'string', 144 | enum: ['active', 'paused', 'completed'], 145 | description: 'Filter campaigns by status', 146 | }, 147 | limit: { 148 | type: 'number', 149 | description: 'Maximum number of campaigns to return', 150 | }, 151 | offset: { 152 | type: 'number', 153 | description: 'Offset for pagination', 154 | }, 155 | }, 156 | }, 157 | }; 158 | 159 | export const SAVE_CAMPAIGN_SEQUENCE_TOOL: CategoryTool = { 160 | name: 'smartlead_save_campaign_sequence', 161 | description: 'Save a sequence of emails for a campaign.', 162 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 163 | inputSchema: { 164 | type: 'object', 165 | properties: { 166 | campaign_id: { 167 | type: 'number', 168 | description: 'ID of the campaign', 169 | }, 170 | sequence: { 171 | type: 'array', 172 | items: { 173 | type: 'object', 174 | properties: { 175 | seq_number: { 176 | type: 'number', 177 | description: 'The sequence number (order) of this email', 178 | }, 179 | seq_delay_details: { 180 | type: 'object', 181 | properties: { 182 | delay_in_days: { 183 | type: 'number', 184 | description: 'Days to wait before sending this email', 185 | } 186 | }, 187 | description: 'Delay details for this sequence' 188 | }, 189 | variant_distribution_type: { 190 | type: 'string', 191 | enum: ['MANUAL_EQUAL', 'MANUAL_PERCENTAGE', 'AI_EQUAL'], 192 | description: 'How to distribute variants' 193 | }, 194 | lead_distribution_percentage: { 195 | type: 'number', 196 | description: 'What sample % size of the lead pool to use to find the winner (for AI_EQUAL)' 197 | }, 198 | winning_metric_property: { 199 | type: 'string', 200 | enum: ['OPEN_RATE', 'CLICK_RATE', 'REPLY_RATE', 'POSITIVE_REPLY_RATE'], 201 | description: 'Metric to use for determining the winning variant (for AI_EQUAL)' 202 | }, 203 | seq_variants: { 204 | type: 'array', 205 | items: { 206 | type: 'object', 207 | properties: { 208 | subject: { 209 | type: 'string', 210 | description: 'Email subject line', 211 | }, 212 | email_body: { 213 | type: 'string', 214 | description: 'Email body content in HTML', 215 | }, 216 | variant_label: { 217 | type: 'string', 218 | description: 'Label for this variant (A, B, C, etc.)', 219 | }, 220 | variant_distribution_percentage: { 221 | type: 'number', 222 | description: 'Percentage of leads to receive this variant (for MANUAL_PERCENTAGE)' 223 | } 224 | }, 225 | required: ['subject', 'email_body', 'variant_label'], 226 | }, 227 | description: 'Variants of the email in this sequence' 228 | } 229 | }, 230 | required: ['seq_number', 'seq_delay_details', 'variant_distribution_type', 'seq_variants'], 231 | }, 232 | description: 'Sequence of emails to send', 233 | }, 234 | }, 235 | required: ['campaign_id', 'sequence'], 236 | }, 237 | }; 238 | 239 | export const GET_CAMPAIGN_SEQUENCE_TOOL: CategoryTool = { 240 | name: 'smartlead_get_campaign_sequence', 241 | description: 'Fetch a campaign\'s sequence data.', 242 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 243 | inputSchema: { 244 | type: 'object', 245 | properties: { 246 | campaign_id: { 247 | type: 'number', 248 | description: 'ID of the campaign to fetch sequences for', 249 | }, 250 | }, 251 | required: ['campaign_id'], 252 | }, 253 | }; 254 | 255 | // New tool definitions for the remaining campaign management API endpoints 256 | 257 | export const GET_CAMPAIGNS_BY_LEAD_TOOL: CategoryTool = { 258 | name: 'smartlead_get_campaigns_by_lead', 259 | description: 'Fetch all campaigns that a lead belongs to.', 260 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 261 | inputSchema: { 262 | type: 'object', 263 | properties: { 264 | lead_id: { 265 | type: 'number', 266 | description: 'ID of the lead to fetch campaigns for', 267 | }, 268 | }, 269 | required: ['lead_id'], 270 | }, 271 | }; 272 | 273 | export const EXPORT_CAMPAIGN_LEADS_TOOL: CategoryTool = { 274 | name: 'smartlead_export_campaign_leads', 275 | description: 'Export all leads data from a campaign as CSV.', 276 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 277 | inputSchema: { 278 | type: 'object', 279 | properties: { 280 | campaign_id: { 281 | type: 'number', 282 | description: 'ID of the campaign to export leads from', 283 | }, 284 | }, 285 | required: ['campaign_id'], 286 | }, 287 | }; 288 | 289 | export const DELETE_CAMPAIGN_TOOL: CategoryTool = { 290 | name: 'smartlead_delete_campaign', 291 | description: 'Delete a campaign permanently.', 292 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 293 | inputSchema: { 294 | type: 'object', 295 | properties: { 296 | campaign_id: { 297 | type: 'number', 298 | description: 'ID of the campaign to delete', 299 | }, 300 | }, 301 | required: ['campaign_id'], 302 | }, 303 | }; 304 | 305 | export const GET_CAMPAIGN_ANALYTICS_BY_DATE_TOOL: CategoryTool = { 306 | name: 'smartlead_get_campaign_analytics_by_date', 307 | description: 'Fetch campaign analytics for a specific date range.', 308 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 309 | inputSchema: { 310 | type: 'object', 311 | properties: { 312 | campaign_id: { 313 | type: 'number', 314 | description: 'ID of the campaign to fetch analytics for', 315 | }, 316 | start_date: { 317 | type: 'string', 318 | format: 'date', 319 | description: 'Start date in YYYY-MM-DD format', 320 | }, 321 | end_date: { 322 | type: 'string', 323 | format: 'date', 324 | description: 'End date in YYYY-MM-DD format', 325 | }, 326 | }, 327 | required: ['campaign_id', 'start_date', 'end_date'], 328 | }, 329 | }; 330 | 331 | export const GET_CAMPAIGN_SEQUENCE_ANALYTICS_TOOL: CategoryTool = { 332 | name: 'smartlead_get_campaign_sequence_analytics', 333 | description: 'Fetch analytics data for a specific email campaign sequence.', 334 | category: ToolCategory.CAMPAIGN_MANAGEMENT, 335 | inputSchema: { 336 | type: 'object', 337 | properties: { 338 | campaign_id: { 339 | type: 'number', 340 | description: 'ID of the campaign to fetch sequence analytics for', 341 | }, 342 | start_date: { 343 | type: 'string', 344 | description: 'Start date in YYYY-MM-DD HH:MM:SS format', 345 | }, 346 | end_date: { 347 | type: 'string', 348 | description: 'End date in YYYY-MM-DD HH:MM:SS format', 349 | }, 350 | time_zone: { 351 | type: 'string', 352 | description: 'Timezone for the analytics data (e.g., "Europe/London")', 353 | }, 354 | }, 355 | required: ['campaign_id', 'start_date', 'end_date'], 356 | }, 357 | }; 358 | 359 | // Export an array of all campaign tools for registration 360 | export const campaignTools = [ 361 | CREATE_CAMPAIGN_TOOL, 362 | UPDATE_CAMPAIGN_SCHEDULE_TOOL, 363 | UPDATE_CAMPAIGN_SETTINGS_TOOL, 364 | UPDATE_CAMPAIGN_STATUS_TOOL, 365 | GET_CAMPAIGN_TOOL, 366 | LIST_CAMPAIGNS_TOOL, 367 | SAVE_CAMPAIGN_SEQUENCE_TOOL, 368 | GET_CAMPAIGN_SEQUENCE_TOOL, 369 | GET_CAMPAIGNS_BY_LEAD_TOOL, 370 | EXPORT_CAMPAIGN_LEADS_TOOL, 371 | DELETE_CAMPAIGN_TOOL, 372 | GET_CAMPAIGN_ANALYTICS_BY_DATE_TOOL, 373 | GET_CAMPAIGN_SEQUENCE_ANALYTICS_TOOL 374 | ]; ``` -------------------------------------------------------------------------------- /src/types/smartDelivery.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Type definitions for SmartDelivery functionality 2 | 3 | // Region wise Provider IDs response types 4 | export interface SpamTestProvider { 5 | id: number; 6 | name: string; 7 | description?: string; 8 | region?: string; 9 | country?: string; 10 | is_active: boolean; 11 | } 12 | 13 | export interface RegionWiseProvidersResponse { 14 | success: boolean; 15 | data: { 16 | providers: SpamTestProvider[]; 17 | }; 18 | message?: string; 19 | } 20 | 21 | // Manual Placement Test types 22 | export interface CreateManualPlacementTestParams { 23 | test_name: string; 24 | description?: string; 25 | spam_filters: string[]; 26 | link_checker: boolean; 27 | campaign_id: number; 28 | sequence_mapping_id: number; 29 | provider_ids: number[]; 30 | sender_accounts: string[]; 31 | all_email_sent_without_time_gap: boolean; 32 | min_time_btwn_emails: number; 33 | min_time_unit: string; 34 | is_warmup: boolean; 35 | } 36 | 37 | // Automated Placement Test additional types 38 | export interface CreateAutomatedPlacementTestParams extends CreateManualPlacementTestParams { 39 | schedule_start_time: string; 40 | test_end_date: string; 41 | every_days: number; 42 | tz: string; 43 | days: number[]; 44 | starHour?: string; 45 | folder_id?: number; 46 | scheduler_cron_value?: { 47 | tz: string; 48 | days: number[]; 49 | }; 50 | } 51 | 52 | // Spam Test Details types 53 | export interface GetSpamTestDetailsParams { 54 | spam_test_id: number; 55 | } 56 | 57 | // Delete Smart Delivery Tests types 58 | export interface DeleteSmartDeliveryTestsParams { 59 | spamTestIds: number[]; 60 | } 61 | 62 | // Stop Automated Test types 63 | export interface StopAutomatedTestParams { 64 | spam_test_id: number; 65 | } 66 | 67 | // List all Tests types 68 | export interface ListAllTestsParams { 69 | testType: 'manual' | 'auto'; 70 | limit?: number; 71 | offset?: number; 72 | } 73 | 74 | // Provider wise report types 75 | export interface ProviderWiseReportParams { 76 | spam_test_id: number; 77 | } 78 | 79 | // Geo wise report types 80 | export interface GroupWiseReportParams { 81 | spam_test_id: number; 82 | } 83 | 84 | // Sender Account wise report types 85 | export interface SenderAccountWiseReportParams { 86 | spam_test_id: number; 87 | } 88 | 89 | // Spam filter report types 90 | export interface SpamFilterDetailsParams { 91 | spam_test_id: number; 92 | } 93 | 94 | // DKIM details types 95 | export interface DkimDetailsParams { 96 | spam_test_id: number; 97 | } 98 | 99 | // SPF details types 100 | export interface SpfDetailsParams { 101 | spam_test_id: number; 102 | } 103 | 104 | // rDNS report types 105 | export interface RdnsDetailsParams { 106 | spam_test_id: number; 107 | } 108 | 109 | // Sender Account list types 110 | export interface SenderAccountsParams { 111 | spam_test_id: number; 112 | } 113 | 114 | // Blacklists types 115 | export interface BlacklistParams { 116 | spam_test_id: number; 117 | } 118 | 119 | // Spam test email content types 120 | export interface EmailContentParams { 121 | spam_test_id: number; 122 | } 123 | 124 | // Spam test IP blacklist count types 125 | export interface IpAnalyticsParams { 126 | spam_test_id: number; 127 | } 128 | 129 | // Email reply headers types 130 | export interface EmailHeadersParams { 131 | spam_test_id: number; 132 | reply_id: number; 133 | } 134 | 135 | // Schedule history for automated tests types 136 | export interface ScheduleHistoryParams { 137 | spam_test_id: number; 138 | } 139 | 140 | // IP details types 141 | export interface IpDetailsParams { 142 | spam_test_id: number; 143 | reply_id: number; 144 | } 145 | 146 | // Mailbox summary types 147 | export interface MailboxSummaryParams { 148 | limit?: number; 149 | offset?: number; 150 | } 151 | 152 | // Mailbox count API types 153 | export interface MailboxCountParams { 154 | // This endpoint doesn't require any specific parameters 155 | } 156 | 157 | // Get all folders types 158 | export interface GetAllFoldersParams { 159 | limit?: number; 160 | offset?: number; 161 | name?: string; 162 | } 163 | 164 | // Create folders types 165 | export interface CreateFolderParams { 166 | name: string; 167 | } 168 | 169 | // Get folder by ID types 170 | export interface GetFolderByIdParams { 171 | folder_id: number; 172 | } 173 | 174 | // Delete folder types 175 | export interface DeleteFolderParams { 176 | folder_id: number; 177 | } 178 | 179 | // Tool parameter interfaces 180 | export interface GetRegionWiseProvidersParams { 181 | // This endpoint doesn't require any specific parameters beyond the API key 182 | // which is handled at the API client level 183 | } 184 | 185 | // Type guards 186 | export function isGetRegionWiseProvidersParams(args: unknown): args is GetRegionWiseProvidersParams { 187 | // Since this tool doesn't require specific parameters, any object is valid 188 | return typeof args === 'object' && args !== null; 189 | } 190 | 191 | export function isCreateManualPlacementTestParams(args: unknown): args is CreateManualPlacementTestParams { 192 | if (typeof args !== 'object' || args === null) return false; 193 | 194 | const params = args as Partial<CreateManualPlacementTestParams>; 195 | 196 | return ( 197 | typeof params.test_name === 'string' && 198 | Array.isArray(params.spam_filters) && 199 | typeof params.link_checker === 'boolean' && 200 | typeof params.campaign_id === 'number' && 201 | typeof params.sequence_mapping_id === 'number' && 202 | Array.isArray(params.provider_ids) && 203 | Array.isArray(params.sender_accounts) && 204 | typeof params.all_email_sent_without_time_gap === 'boolean' && 205 | typeof params.min_time_btwn_emails === 'number' && 206 | typeof params.min_time_unit === 'string' && 207 | typeof params.is_warmup === 'boolean' 208 | ); 209 | } 210 | 211 | export function isCreateAutomatedPlacementTestParams(args: unknown): args is CreateAutomatedPlacementTestParams { 212 | if (!isCreateManualPlacementTestParams(args)) return false; 213 | 214 | const params = args as Partial<CreateAutomatedPlacementTestParams>; 215 | 216 | return ( 217 | typeof params.schedule_start_time === 'string' && 218 | typeof params.test_end_date === 'string' && 219 | typeof params.every_days === 'number' && 220 | typeof params.tz === 'string' && 221 | Array.isArray(params.days) 222 | ); 223 | } 224 | 225 | export function isGetSpamTestDetailsParams(args: unknown): args is GetSpamTestDetailsParams { 226 | return ( 227 | typeof args === 'object' && 228 | args !== null && 229 | 'spam_test_id' in args && 230 | typeof (args as GetSpamTestDetailsParams).spam_test_id === 'number' 231 | ); 232 | } 233 | 234 | export function isDeleteSmartDeliveryTestsParams(args: unknown): args is DeleteSmartDeliveryTestsParams { 235 | return ( 236 | typeof args === 'object' && 237 | args !== null && 238 | 'spamTestIds' in args && 239 | Array.isArray((args as DeleteSmartDeliveryTestsParams).spamTestIds) && 240 | (args as DeleteSmartDeliveryTestsParams).spamTestIds.every(id => typeof id === 'number') 241 | ); 242 | } 243 | 244 | export function isStopAutomatedTestParams(args: unknown): args is StopAutomatedTestParams { 245 | return ( 246 | typeof args === 'object' && 247 | args !== null && 248 | 'spam_test_id' in args && 249 | typeof (args as StopAutomatedTestParams).spam_test_id === 'number' 250 | ); 251 | } 252 | 253 | export function isListAllTestsParams(args: unknown): args is ListAllTestsParams { 254 | if (typeof args !== 'object' || args === null) return false; 255 | 256 | const params = args as Partial<ListAllTestsParams>; 257 | 258 | return ( 259 | params.testType === 'manual' || params.testType === 'auto' 260 | ); 261 | } 262 | 263 | export function isProviderWiseReportParams(args: unknown): args is ProviderWiseReportParams { 264 | return ( 265 | typeof args === 'object' && 266 | args !== null && 267 | 'spam_test_id' in args && 268 | typeof (args as ProviderWiseReportParams).spam_test_id === 'number' 269 | ); 270 | } 271 | 272 | export function isGroupWiseReportParams(args: unknown): args is GroupWiseReportParams { 273 | return ( 274 | typeof args === 'object' && 275 | args !== null && 276 | 'spam_test_id' in args && 277 | typeof (args as GroupWiseReportParams).spam_test_id === 'number' 278 | ); 279 | } 280 | 281 | export function isSenderAccountWiseReportParams(args: unknown): args is SenderAccountWiseReportParams { 282 | return ( 283 | typeof args === 'object' && 284 | args !== null && 285 | 'spam_test_id' in args && 286 | typeof (args as SenderAccountWiseReportParams).spam_test_id === 'number' 287 | ); 288 | } 289 | 290 | export function isSpamFilterDetailsParams(args: unknown): args is SpamFilterDetailsParams { 291 | return ( 292 | typeof args === 'object' && 293 | args !== null && 294 | 'spam_test_id' in args && 295 | typeof (args as SpamFilterDetailsParams).spam_test_id === 'number' 296 | ); 297 | } 298 | 299 | export function isDkimDetailsParams(args: unknown): args is DkimDetailsParams { 300 | return ( 301 | typeof args === 'object' && 302 | args !== null && 303 | 'spam_test_id' in args && 304 | typeof (args as DkimDetailsParams).spam_test_id === 'number' 305 | ); 306 | } 307 | 308 | export function isSpfDetailsParams(args: unknown): args is SpfDetailsParams { 309 | return ( 310 | typeof args === 'object' && 311 | args !== null && 312 | 'spam_test_id' in args && 313 | typeof (args as SpfDetailsParams).spam_test_id === 'number' 314 | ); 315 | } 316 | 317 | export function isRdnsDetailsParams(args: unknown): args is RdnsDetailsParams { 318 | return ( 319 | typeof args === 'object' && 320 | args !== null && 321 | 'spam_test_id' in args && 322 | typeof (args as RdnsDetailsParams).spam_test_id === 'number' 323 | ); 324 | } 325 | 326 | export function isSenderAccountsParams(args: unknown): args is SenderAccountsParams { 327 | return ( 328 | typeof args === 'object' && 329 | args !== null && 330 | 'spam_test_id' in args && 331 | typeof (args as SenderAccountsParams).spam_test_id === 'number' 332 | ); 333 | } 334 | 335 | export function isBlacklistParams(args: unknown): args is BlacklistParams { 336 | return ( 337 | typeof args === 'object' && 338 | args !== null && 339 | 'spam_test_id' in args && 340 | typeof (args as BlacklistParams).spam_test_id === 'number' 341 | ); 342 | } 343 | 344 | export function isEmailContentParams(args: unknown): args is EmailContentParams { 345 | return ( 346 | typeof args === 'object' && 347 | args !== null && 348 | 'spam_test_id' in args && 349 | typeof (args as EmailContentParams).spam_test_id === 'number' 350 | ); 351 | } 352 | 353 | export function isIpAnalyticsParams(args: unknown): args is IpAnalyticsParams { 354 | return ( 355 | typeof args === 'object' && 356 | args !== null && 357 | 'spam_test_id' in args && 358 | typeof (args as IpAnalyticsParams).spam_test_id === 'number' 359 | ); 360 | } 361 | 362 | export function isEmailHeadersParams(args: unknown): args is EmailHeadersParams { 363 | return ( 364 | typeof args === 'object' && 365 | args !== null && 366 | 'spam_test_id' in args && 367 | typeof (args as EmailHeadersParams).spam_test_id === 'number' && 368 | 'reply_id' in args && 369 | typeof (args as EmailHeadersParams).reply_id === 'number' 370 | ); 371 | } 372 | 373 | export function isScheduleHistoryParams(args: unknown): args is ScheduleHistoryParams { 374 | return ( 375 | typeof args === 'object' && 376 | args !== null && 377 | 'spam_test_id' in args && 378 | typeof (args as ScheduleHistoryParams).spam_test_id === 'number' 379 | ); 380 | } 381 | 382 | export function isIpDetailsParams(args: unknown): args is IpDetailsParams { 383 | return ( 384 | typeof args === 'object' && 385 | args !== null && 386 | 'spam_test_id' in args && 387 | typeof (args as IpDetailsParams).spam_test_id === 'number' && 388 | 'reply_id' in args && 389 | typeof (args as IpDetailsParams).reply_id === 'number' 390 | ); 391 | } 392 | 393 | export function isMailboxSummaryParams(args: unknown): args is MailboxSummaryParams { 394 | return typeof args === 'object' && args !== null; 395 | } 396 | 397 | export function isMailboxCountParams(args: unknown): args is MailboxCountParams { 398 | return typeof args === 'object' && args !== null; 399 | } 400 | 401 | export function isGetAllFoldersParams(args: unknown): args is GetAllFoldersParams { 402 | return typeof args === 'object' && args !== null; 403 | } 404 | 405 | export function isCreateFolderParams(args: unknown): args is CreateFolderParams { 406 | return ( 407 | typeof args === 'object' && 408 | args !== null && 409 | 'name' in args && 410 | typeof (args as CreateFolderParams).name === 'string' 411 | ); 412 | } 413 | 414 | export function isGetFolderByIdParams(args: unknown): args is GetFolderByIdParams { 415 | return ( 416 | typeof args === 'object' && 417 | args !== null && 418 | 'folder_id' in args && 419 | typeof (args as GetFolderByIdParams).folder_id === 'number' 420 | ); 421 | } 422 | 423 | export function isDeleteFolderParams(args: unknown): args is DeleteFolderParams { 424 | return ( 425 | typeof args === 'object' && 426 | args !== null && 427 | 'folder_id' in args && 428 | typeof (args as DeleteFolderParams).folder_id === 'number' 429 | ); 430 | } 431 | ``` -------------------------------------------------------------------------------- /src/handlers/campaign.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { AxiosInstance } from 'axios'; 2 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; 3 | import { 4 | isCreateCampaignParams, 5 | isUpdateCampaignScheduleParams, 6 | isUpdateCampaignSettingsParams, 7 | isUpdateCampaignStatusParams, 8 | isGetCampaignParams, 9 | isListCampaignsParams, 10 | isSaveCampaignSequenceParams, 11 | isGetCampaignSequenceParams, 12 | isGetCampaignsByLeadParams, 13 | isExportCampaignLeadsParams, 14 | isDeleteCampaignParams, 15 | isGetCampaignAnalyticsByDateParams, 16 | isGetCampaignSequenceAnalyticsParams 17 | } from '../types/campaign.js'; 18 | 19 | // Handler for campaign-related tools 20 | export async function handleCampaignTool( 21 | toolName: string, 22 | args: unknown, 23 | apiClient: AxiosInstance, 24 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 25 | ) { 26 | switch (toolName) { 27 | case 'smartlead_create_campaign': { 28 | return handleCreateCampaign(args, apiClient, withRetry); 29 | } 30 | case 'smartlead_update_campaign_schedule': { 31 | return handleUpdateCampaignSchedule(args, apiClient, withRetry); 32 | } 33 | case 'smartlead_update_campaign_settings': { 34 | return handleUpdateCampaignSettings(args, apiClient, withRetry); 35 | } 36 | case 'smartlead_update_campaign_status': { 37 | return handleUpdateCampaignStatus(args, apiClient, withRetry); 38 | } 39 | case 'smartlead_get_campaign': { 40 | return handleGetCampaign(args, apiClient, withRetry); 41 | } 42 | case 'smartlead_list_campaigns': { 43 | return handleListCampaigns(args, apiClient, withRetry); 44 | } 45 | case 'smartlead_save_campaign_sequence': { 46 | return handleSaveCampaignSequence(args, apiClient, withRetry); 47 | } 48 | case 'smartlead_get_campaign_sequence': { 49 | return handleGetCampaignSequence(args, apiClient, withRetry); 50 | } 51 | case 'smartlead_get_campaigns_by_lead': { 52 | return handleGetCampaignsByLead(args, apiClient, withRetry); 53 | } 54 | case 'smartlead_export_campaign_leads': { 55 | return handleExportCampaignLeads(args, apiClient, withRetry); 56 | } 57 | case 'smartlead_delete_campaign': { 58 | return handleDeleteCampaign(args, apiClient, withRetry); 59 | } 60 | case 'smartlead_get_campaign_analytics_by_date': { 61 | return handleGetCampaignAnalyticsByDate(args, apiClient, withRetry); 62 | } 63 | case 'smartlead_get_campaign_sequence_analytics': { 64 | return handleGetCampaignSequenceAnalytics(args, apiClient, withRetry); 65 | } 66 | default: 67 | throw new Error(`Unknown campaign tool: ${toolName}`); 68 | } 69 | } 70 | 71 | // Individual handlers for each tool 72 | async function handleCreateCampaign( 73 | args: unknown, 74 | apiClient: AxiosInstance, 75 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 76 | ) { 77 | if (!isCreateCampaignParams(args)) { 78 | throw new McpError( 79 | ErrorCode.InvalidParams, 80 | 'Invalid arguments for smartlead_create_campaign' 81 | ); 82 | } 83 | 84 | try { 85 | const response = await withRetry( 86 | async () => apiClient.post('/campaigns/create', args), 87 | 'create campaign' 88 | ); 89 | 90 | return { 91 | content: [ 92 | { 93 | type: 'text', 94 | text: JSON.stringify(response.data, null, 2), 95 | }, 96 | ], 97 | isError: false, 98 | }; 99 | } catch (error: any) { 100 | return { 101 | content: [{ 102 | type: 'text', 103 | text: `API Error: ${error.response?.data?.message || error.message}` 104 | }], 105 | isError: true, 106 | }; 107 | } 108 | } 109 | 110 | async function handleUpdateCampaignSchedule( 111 | args: unknown, 112 | apiClient: AxiosInstance, 113 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 114 | ) { 115 | if (!isUpdateCampaignScheduleParams(args)) { 116 | throw new McpError( 117 | ErrorCode.InvalidParams, 118 | 'Invalid arguments for smartlead_update_campaign_schedule' 119 | ); 120 | } 121 | 122 | const { campaign_id, ...scheduleParams } = args; 123 | 124 | try { 125 | const response = await withRetry( 126 | async () => apiClient.post(`/campaigns/${campaign_id}/schedule`, scheduleParams), 127 | 'update campaign schedule' 128 | ); 129 | 130 | return { 131 | content: [ 132 | { 133 | type: 'text', 134 | text: JSON.stringify(response.data, null, 2), 135 | }, 136 | ], 137 | isError: false, 138 | }; 139 | } catch (error: any) { 140 | return { 141 | content: [{ 142 | type: 'text', 143 | text: `API Error: ${error.response?.data?.message || error.message}` 144 | }], 145 | isError: true, 146 | }; 147 | } 148 | } 149 | 150 | async function handleUpdateCampaignSettings( 151 | args: unknown, 152 | apiClient: AxiosInstance, 153 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 154 | ) { 155 | if (!isUpdateCampaignSettingsParams(args)) { 156 | throw new McpError( 157 | ErrorCode.InvalidParams, 158 | 'Invalid arguments for smartlead_update_campaign_settings' 159 | ); 160 | } 161 | 162 | const { campaign_id, ...settingsParams } = args; 163 | 164 | try { 165 | const response = await withRetry( 166 | async () => apiClient.post(`/campaigns/${campaign_id}/settings`, settingsParams), 167 | 'update campaign settings' 168 | ); 169 | 170 | return { 171 | content: [ 172 | { 173 | type: 'text', 174 | text: JSON.stringify(response.data, null, 2), 175 | }, 176 | ], 177 | isError: false, 178 | }; 179 | } catch (error: any) { 180 | return { 181 | content: [{ 182 | type: 'text', 183 | text: `API Error: ${error.response?.data?.message || error.message}` 184 | }], 185 | isError: true, 186 | }; 187 | } 188 | } 189 | 190 | async function handleUpdateCampaignStatus( 191 | args: unknown, 192 | apiClient: AxiosInstance, 193 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 194 | ) { 195 | if (!isUpdateCampaignStatusParams(args)) { 196 | throw new McpError( 197 | ErrorCode.InvalidParams, 198 | 'Invalid arguments for smartlead_update_campaign_status' 199 | ); 200 | } 201 | 202 | const { campaign_id, status } = args; 203 | 204 | try { 205 | const response = await withRetry( 206 | async () => apiClient.post(`/campaigns/${campaign_id}/status`, { status }), 207 | 'update campaign status' 208 | ); 209 | 210 | return { 211 | content: [ 212 | { 213 | type: 'text', 214 | text: JSON.stringify(response.data, null, 2), 215 | }, 216 | ], 217 | isError: false, 218 | }; 219 | } catch (error: any) { 220 | return { 221 | content: [{ 222 | type: 'text', 223 | text: `API Error: ${error.response?.data?.message || error.message}` 224 | }], 225 | isError: true, 226 | }; 227 | } 228 | } 229 | 230 | async function handleGetCampaign( 231 | args: unknown, 232 | apiClient: AxiosInstance, 233 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 234 | ) { 235 | if (!isGetCampaignParams(args)) { 236 | throw new McpError( 237 | ErrorCode.InvalidParams, 238 | 'Invalid arguments for smartlead_get_campaign' 239 | ); 240 | } 241 | 242 | try { 243 | const response = await withRetry( 244 | async () => apiClient.get(`/campaigns/${args.campaign_id}`), 245 | 'get campaign' 246 | ); 247 | 248 | return { 249 | content: [ 250 | { 251 | type: 'text', 252 | text: JSON.stringify(response.data, null, 2), 253 | }, 254 | ], 255 | isError: false, 256 | }; 257 | } catch (error: any) { 258 | return { 259 | content: [{ 260 | type: 'text', 261 | text: `API Error: ${error.response?.data?.message || error.message}` 262 | }], 263 | isError: true, 264 | }; 265 | } 266 | } 267 | 268 | async function handleListCampaigns( 269 | args: unknown, 270 | apiClient: AxiosInstance, 271 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 272 | ) { 273 | if (!isListCampaignsParams(args)) { 274 | throw new McpError( 275 | ErrorCode.InvalidParams, 276 | 'Invalid arguments for smartlead_list_campaigns' 277 | ); 278 | } 279 | 280 | try { 281 | const response = await withRetry( 282 | async () => apiClient.get('/campaigns', { params: args }), 283 | 'list campaigns' 284 | ); 285 | 286 | return { 287 | content: [ 288 | { 289 | type: 'text', 290 | text: JSON.stringify(response.data, null, 2), 291 | }, 292 | ], 293 | isError: false, 294 | }; 295 | } catch (error: any) { 296 | return { 297 | content: [{ 298 | type: 'text', 299 | text: `API Error: ${error.response?.data?.message || error.message}` 300 | }], 301 | isError: true, 302 | }; 303 | } 304 | } 305 | 306 | async function handleSaveCampaignSequence( 307 | args: unknown, 308 | apiClient: AxiosInstance, 309 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 310 | ) { 311 | if (!isSaveCampaignSequenceParams(args)) { 312 | throw new McpError( 313 | ErrorCode.InvalidParams, 314 | 'Invalid arguments for smartlead_save_campaign_sequence' 315 | ); 316 | } 317 | 318 | const { campaign_id, sequence } = args; 319 | 320 | try { 321 | const response = await withRetry( 322 | async () => apiClient.post(`/campaigns/${campaign_id}/sequences`, { sequence }), 323 | 'save campaign sequence' 324 | ); 325 | 326 | return { 327 | content: [ 328 | { 329 | type: 'text', 330 | text: JSON.stringify(response.data, null, 2), 331 | }, 332 | ], 333 | isError: false, 334 | }; 335 | } catch (error: any) { 336 | return { 337 | content: [{ 338 | type: 'text', 339 | text: `API Error: ${error.response?.data?.message || error.message}` 340 | }], 341 | isError: true, 342 | }; 343 | } 344 | } 345 | 346 | async function handleGetCampaignSequence( 347 | args: unknown, 348 | apiClient: AxiosInstance, 349 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 350 | ) { 351 | if (!isGetCampaignSequenceParams(args)) { 352 | throw new McpError( 353 | ErrorCode.InvalidParams, 354 | 'Invalid arguments for smartlead_get_campaign_sequence' 355 | ); 356 | } 357 | 358 | try { 359 | const response = await withRetry( 360 | async () => apiClient.get(`/campaigns/${args.campaign_id}/sequences`), 361 | 'get campaign sequence' 362 | ); 363 | 364 | return { 365 | content: [ 366 | { 367 | type: 'text', 368 | text: JSON.stringify(response.data, null, 2), 369 | }, 370 | ], 371 | isError: false, 372 | }; 373 | } catch (error: any) { 374 | return { 375 | content: [{ 376 | type: 'text', 377 | text: `API Error: ${error.response?.data?.message || error.message}` 378 | }], 379 | isError: true, 380 | }; 381 | } 382 | } 383 | 384 | // New handler implementations for the remaining campaign management API endpoints 385 | async function handleGetCampaignsByLead( 386 | args: unknown, 387 | apiClient: AxiosInstance, 388 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 389 | ) { 390 | if (!isGetCampaignsByLeadParams(args)) { 391 | throw new McpError( 392 | ErrorCode.InvalidParams, 393 | 'Invalid arguments for smartlead_get_campaigns_by_lead' 394 | ); 395 | } 396 | 397 | try { 398 | const response = await withRetry( 399 | async () => apiClient.get(`/leads/${args.lead_id}/campaigns`), 400 | 'get campaigns by lead' 401 | ); 402 | 403 | return { 404 | content: [ 405 | { 406 | type: 'text', 407 | text: JSON.stringify(response.data, null, 2), 408 | }, 409 | ], 410 | isError: false, 411 | }; 412 | } catch (error: any) { 413 | return { 414 | content: [{ 415 | type: 'text', 416 | text: `API Error: ${error.response?.data?.message || error.message}` 417 | }], 418 | isError: true, 419 | }; 420 | } 421 | } 422 | 423 | async function handleExportCampaignLeads( 424 | args: unknown, 425 | apiClient: AxiosInstance, 426 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 427 | ) { 428 | if (!isExportCampaignLeadsParams(args)) { 429 | throw new McpError( 430 | ErrorCode.InvalidParams, 431 | 'Invalid arguments for smartlead_export_campaign_leads' 432 | ); 433 | } 434 | 435 | try { 436 | const response = await withRetry( 437 | async () => apiClient.get(`/campaigns/${args.campaign_id}/leads-export`, { 438 | responseType: 'text' 439 | }), 440 | 'export campaign leads' 441 | ); 442 | 443 | return { 444 | content: [ 445 | { 446 | type: 'text', 447 | text: `CSV Data:\n${response.data}`, 448 | }, 449 | ], 450 | isError: false, 451 | }; 452 | } catch (error: any) { 453 | return { 454 | content: [{ 455 | type: 'text', 456 | text: `API Error: ${error.response?.data?.message || error.message}` 457 | }], 458 | isError: true, 459 | }; 460 | } 461 | } 462 | 463 | async function handleDeleteCampaign( 464 | args: unknown, 465 | apiClient: AxiosInstance, 466 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 467 | ) { 468 | if (!isDeleteCampaignParams(args)) { 469 | throw new McpError( 470 | ErrorCode.InvalidParams, 471 | 'Invalid arguments for smartlead_delete_campaign' 472 | ); 473 | } 474 | 475 | try { 476 | const response = await withRetry( 477 | async () => apiClient.delete(`/campaigns/${args.campaign_id}`), 478 | 'delete campaign' 479 | ); 480 | 481 | return { 482 | content: [ 483 | { 484 | type: 'text', 485 | text: JSON.stringify(response.data, null, 2), 486 | }, 487 | ], 488 | isError: false, 489 | }; 490 | } catch (error: any) { 491 | return { 492 | content: [{ 493 | type: 'text', 494 | text: `API Error: ${error.response?.data?.message || error.message}` 495 | }], 496 | isError: true, 497 | }; 498 | } 499 | } 500 | 501 | async function handleGetCampaignAnalyticsByDate( 502 | args: unknown, 503 | apiClient: AxiosInstance, 504 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 505 | ) { 506 | if (!isGetCampaignAnalyticsByDateParams(args)) { 507 | throw new McpError( 508 | ErrorCode.InvalidParams, 509 | 'Invalid arguments for smartlead_get_campaign_analytics_by_date' 510 | ); 511 | } 512 | 513 | const { campaign_id, ...params } = args; 514 | 515 | try { 516 | const response = await withRetry( 517 | async () => apiClient.get(`/campaigns/${campaign_id}/analytics-by-date`, { 518 | params 519 | }), 520 | 'get campaign analytics by date' 521 | ); 522 | 523 | return { 524 | content: [ 525 | { 526 | type: 'text', 527 | text: JSON.stringify(response.data, null, 2), 528 | }, 529 | ], 530 | isError: false, 531 | }; 532 | } catch (error: any) { 533 | return { 534 | content: [{ 535 | type: 'text', 536 | text: `API Error: ${error.response?.data?.message || error.message}` 537 | }], 538 | isError: true, 539 | }; 540 | } 541 | } 542 | 543 | async function handleGetCampaignSequenceAnalytics( 544 | args: unknown, 545 | apiClient: AxiosInstance, 546 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 547 | ) { 548 | if (!isGetCampaignSequenceAnalyticsParams(args)) { 549 | throw new McpError( 550 | ErrorCode.InvalidParams, 551 | 'Invalid arguments for smartlead_get_campaign_sequence_analytics' 552 | ); 553 | } 554 | 555 | const { campaign_id, ...params } = args; 556 | 557 | try { 558 | const response = await withRetry( 559 | async () => apiClient.get(`/campaigns/${campaign_id}/sequence-analytics`, { 560 | params 561 | }), 562 | 'get campaign sequence analytics' 563 | ); 564 | 565 | return { 566 | content: [ 567 | { 568 | type: 'text', 569 | text: JSON.stringify(response.data, null, 2), 570 | }, 571 | ], 572 | isError: false, 573 | }; 574 | } catch (error: any) { 575 | return { 576 | content: [{ 577 | type: 'text', 578 | text: `API Error: ${error.response?.data?.message || error.message}` 579 | }], 580 | isError: true, 581 | }; 582 | } 583 | } ``` -------------------------------------------------------------------------------- /src/handlers/email.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { AxiosInstance } from 'axios'; 2 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; 3 | import { 4 | isListEmailAccountsParams, 5 | isAddEmailToCampaignParams, 6 | isRemoveEmailFromCampaignParams, 7 | isFetchEmailAccountsParams, 8 | isCreateEmailAccountParams, 9 | isUpdateEmailAccountParams, 10 | isFetchEmailAccountByIdParams, 11 | isUpdateEmailWarmupParams, 12 | isReconnectEmailAccountParams, 13 | isUpdateEmailAccountTagParams, 14 | ListEmailAccountsParams, 15 | AddEmailToCampaignParams, 16 | RemoveEmailFromCampaignParams, 17 | FetchEmailAccountsParams, 18 | UpdateEmailAccountParams, 19 | FetchEmailAccountByIdParams, 20 | UpdateEmailWarmupParams, 21 | UpdateEmailAccountTagParams, 22 | CreateEmailAccountParams 23 | } from '../types/email.js'; 24 | 25 | // Handler for email-related tools 26 | export async function handleEmailTool( 27 | toolName: string, 28 | args: unknown, 29 | apiClient: AxiosInstance, 30 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 31 | ) { 32 | switch (toolName) { 33 | case 'smartlead_list_email_accounts_campaign': { 34 | return handleListEmailAccountsCampaign(args, apiClient, withRetry); 35 | } 36 | case 'smartlead_add_email_to_campaign': { 37 | return handleAddEmailToCampaign(args, apiClient, withRetry); 38 | } 39 | case 'smartlead_remove_email_from_campaign': { 40 | return handleRemoveEmailFromCampaign(args, apiClient, withRetry); 41 | } 42 | case 'smartlead_fetch_email_accounts': { 43 | return handleFetchEmailAccounts(args, apiClient, withRetry); 44 | } 45 | case 'smartlead_create_email_account': { 46 | return handleCreateEmailAccount(args, apiClient, withRetry); 47 | } 48 | case 'smartlead_update_email_account': { 49 | return handleUpdateEmailAccount(args, apiClient, withRetry); 50 | } 51 | case 'smartlead_fetch_email_account_by_id': { 52 | return handleFetchEmailAccountById(args, apiClient, withRetry); 53 | } 54 | case 'smartlead_update_email_warmup': { 55 | return handleUpdateEmailWarmup(args, apiClient, withRetry); 56 | } 57 | case 'smartlead_reconnect_email_account': { 58 | return handleReconnectEmailAccount(args, apiClient, withRetry); 59 | } 60 | case 'smartlead_update_email_account_tag': { 61 | return handleUpdateEmailAccountTag(args, apiClient, withRetry); 62 | } 63 | default: 64 | throw new Error(`Unknown email tool: ${toolName}`); 65 | } 66 | } 67 | 68 | // Individual handlers for each tool 69 | async function handleListEmailAccountsCampaign( 70 | args: unknown, 71 | apiClient: AxiosInstance, 72 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 73 | ) { 74 | if (!isListEmailAccountsParams(args)) { 75 | throw new McpError( 76 | ErrorCode.InvalidParams, 77 | 'Invalid arguments for smartlead_list_email_accounts_campaign' 78 | ); 79 | } 80 | 81 | const params = args as ListEmailAccountsParams; 82 | const { campaign_id, status, limit, offset } = params; 83 | 84 | if (!campaign_id) { 85 | throw new McpError( 86 | ErrorCode.InvalidParams, 87 | 'campaign_id is required for smartlead_list_email_accounts_campaign' 88 | ); 89 | } 90 | 91 | // API endpoint: https://server.smartlead.ai/api/v1/campaigns/{campaign_id}/email-accounts 92 | try { 93 | const response = await withRetry( 94 | async () => apiClient.get(`/campaigns/${campaign_id}/email-accounts`), 95 | 'list email accounts for campaign' 96 | ); 97 | 98 | return { 99 | content: [ 100 | { 101 | type: 'text', 102 | text: JSON.stringify(response.data, null, 2), 103 | }, 104 | ], 105 | isError: false, 106 | }; 107 | } catch (error: any) { 108 | return { 109 | content: [{ 110 | type: 'text', 111 | text: `API Error: ${error.response?.data?.message || error.message}` 112 | }], 113 | isError: true, 114 | }; 115 | } 116 | } 117 | 118 | // Placeholder functions for the other handlers 119 | // These will be implemented once we have the API documentation for these endpoints 120 | 121 | async function handleAddEmailToCampaign( 122 | args: unknown, 123 | apiClient: AxiosInstance, 124 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 125 | ) { 126 | if (!isAddEmailToCampaignParams(args)) { 127 | throw new McpError( 128 | ErrorCode.InvalidParams, 129 | 'Invalid arguments for smartlead_add_email_to_campaign' 130 | ); 131 | } 132 | 133 | const { campaign_id, email_account_id } = args as AddEmailToCampaignParams; 134 | 135 | // API endpoint: https://server.smartlead.ai/api/v1/campaigns/{campaign_id}/email-accounts 136 | try { 137 | const response = await withRetry( 138 | async () => apiClient.post(`/campaigns/${campaign_id}/email-accounts`, { 139 | email_account_ids: [email_account_id] 140 | }), 141 | 'add email to campaign' 142 | ); 143 | 144 | return { 145 | content: [ 146 | { 147 | type: 'text', 148 | text: JSON.stringify(response.data, null, 2), 149 | }, 150 | ], 151 | isError: false, 152 | }; 153 | } catch (error: any) { 154 | return { 155 | content: [{ 156 | type: 'text', 157 | text: `API Error: ${error.response?.data?.message || error.message}` 158 | }], 159 | isError: true, 160 | }; 161 | } 162 | } 163 | 164 | async function handleRemoveEmailFromCampaign( 165 | args: unknown, 166 | apiClient: AxiosInstance, 167 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 168 | ) { 169 | if (!isRemoveEmailFromCampaignParams(args)) { 170 | throw new McpError( 171 | ErrorCode.InvalidParams, 172 | 'Invalid arguments for smartlead_remove_email_from_campaign' 173 | ); 174 | } 175 | 176 | const { campaign_id, email_account_id } = args as RemoveEmailFromCampaignParams; 177 | 178 | // API endpoint: https://server.smartlead.ai/api/v1/campaigns/{campaign_id}/email-accounts 179 | try { 180 | const response = await withRetry( 181 | async () => apiClient.delete(`/campaigns/${campaign_id}/email-accounts`, { 182 | data: { 183 | email_accounts_ids: [email_account_id] 184 | } 185 | }), 186 | 'remove email from campaign' 187 | ); 188 | 189 | return { 190 | content: [ 191 | { 192 | type: 'text', 193 | text: JSON.stringify(response.data, null, 2), 194 | }, 195 | ], 196 | isError: false, 197 | }; 198 | } catch (error: any) { 199 | return { 200 | content: [{ 201 | type: 'text', 202 | text: `API Error: ${error.response?.data?.message || error.message}` 203 | }], 204 | isError: true, 205 | }; 206 | } 207 | } 208 | 209 | async function handleFetchEmailAccounts( 210 | args: unknown, 211 | apiClient: AxiosInstance, 212 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 213 | ) { 214 | if (!isFetchEmailAccountsParams(args)) { 215 | throw new McpError( 216 | ErrorCode.InvalidParams, 217 | 'Invalid arguments for smartlead_fetch_email_accounts' 218 | ); 219 | } 220 | 221 | const { status, limit, offset, username, client_id } = args as FetchEmailAccountsParams; 222 | 223 | // Build query parameters 224 | const queryParams: Record<string, string | number> = {}; 225 | if (limit !== undefined) queryParams.limit = limit; 226 | if (offset !== undefined) queryParams.offset = offset; 227 | if (username) queryParams.username = username; 228 | if (client_id) queryParams.client_id = client_id; 229 | 230 | // API endpoint: https://server.smartlead.ai/api/v1/email-accounts/ 231 | try { 232 | const response = await withRetry( 233 | async () => apiClient.get('/email-accounts/', { params: queryParams }), 234 | 'fetch all email accounts' 235 | ); 236 | 237 | return { 238 | content: [ 239 | { 240 | type: 'text', 241 | text: JSON.stringify(response.data, null, 2), 242 | }, 243 | ], 244 | isError: false, 245 | }; 246 | } catch (error: any) { 247 | return { 248 | content: [{ 249 | type: 'text', 250 | text: `API Error: ${error.response?.data?.message || error.message}` 251 | }], 252 | isError: true, 253 | }; 254 | } 255 | } 256 | 257 | async function handleCreateEmailAccount( 258 | args: unknown, 259 | apiClient: AxiosInstance, 260 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 261 | ) { 262 | if (!isCreateEmailAccountParams(args)) { 263 | throw new McpError( 264 | ErrorCode.InvalidParams, 265 | 'Invalid arguments for smartlead_create_email_account' 266 | ); 267 | } 268 | 269 | const createParams = args as CreateEmailAccountParams; 270 | 271 | // API endpoint: https://server.smartlead.ai/api/v1/email-accounts/save 272 | try { 273 | const response = await withRetry( 274 | async () => apiClient.post('/email-accounts/save', { 275 | id: null, // Set null to create new email account 276 | from_name: createParams.from_name, 277 | from_email: createParams.from_email, 278 | user_name: createParams.user_name, 279 | password: createParams.password, 280 | smtp_host: createParams.smtp_host, 281 | smtp_port: createParams.smtp_port, 282 | imap_host: createParams.imap_host, 283 | imap_port: createParams.imap_port, 284 | max_email_per_day: createParams.max_email_per_day, 285 | custom_tracking_url: createParams.custom_tracking_url, 286 | bcc: createParams.bcc, 287 | signature: createParams.signature, 288 | warmup_enabled: createParams.warmup_enabled, 289 | total_warmup_per_day: createParams.total_warmup_per_day, 290 | daily_rampup: createParams.daily_rampup, 291 | reply_rate_percentage: createParams.reply_rate_percentage, 292 | client_id: createParams.client_id 293 | }), 294 | 'create email account' 295 | ); 296 | 297 | return { 298 | content: [ 299 | { 300 | type: 'text', 301 | text: JSON.stringify(response.data, null, 2), 302 | }, 303 | ], 304 | isError: false, 305 | }; 306 | } catch (error: any) { 307 | return { 308 | content: [{ 309 | type: 'text', 310 | text: `API Error: ${error.response?.data?.message || error.message}` 311 | }], 312 | isError: true, 313 | }; 314 | } 315 | } 316 | 317 | async function handleUpdateEmailAccount( 318 | args: unknown, 319 | apiClient: AxiosInstance, 320 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 321 | ) { 322 | if (!isUpdateEmailAccountParams(args)) { 323 | throw new McpError( 324 | ErrorCode.InvalidParams, 325 | 'Invalid arguments for smartlead_update_email_account' 326 | ); 327 | } 328 | 329 | const { email_account_id, ...updateParams } = args as UpdateEmailAccountParams; 330 | 331 | // API endpoint: https://server.smartlead.ai/api/v1/email-accounts/{email_account_id} 332 | try { 333 | const response = await withRetry( 334 | async () => apiClient.post(`/email-accounts/${email_account_id}`, updateParams), 335 | 'update email account' 336 | ); 337 | 338 | return { 339 | content: [ 340 | { 341 | type: 'text', 342 | text: JSON.stringify(response.data, null, 2), 343 | }, 344 | ], 345 | isError: false, 346 | }; 347 | } catch (error: any) { 348 | return { 349 | content: [{ 350 | type: 'text', 351 | text: `API Error: ${error.response?.data?.message || error.message}` 352 | }], 353 | isError: true, 354 | }; 355 | } 356 | } 357 | 358 | async function handleFetchEmailAccountById( 359 | args: unknown, 360 | apiClient: AxiosInstance, 361 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 362 | ) { 363 | if (!isFetchEmailAccountByIdParams(args)) { 364 | throw new McpError( 365 | ErrorCode.InvalidParams, 366 | 'Invalid arguments for smartlead_fetch_email_account_by_id' 367 | ); 368 | } 369 | 370 | const { email_account_id } = args as FetchEmailAccountByIdParams; 371 | 372 | // API endpoint: https://server.smartlead.ai/api/v1/email-accounts/{account_id}/ 373 | try { 374 | const response = await withRetry( 375 | async () => apiClient.get(`/email-accounts/${email_account_id}/`), 376 | 'fetch email account by id' 377 | ); 378 | 379 | return { 380 | content: [ 381 | { 382 | type: 'text', 383 | text: JSON.stringify(response.data, null, 2), 384 | }, 385 | ], 386 | isError: false, 387 | }; 388 | } catch (error: any) { 389 | return { 390 | content: [{ 391 | type: 'text', 392 | text: `API Error: ${error.response?.data?.message || error.message}` 393 | }], 394 | isError: true, 395 | }; 396 | } 397 | } 398 | 399 | async function handleUpdateEmailWarmup( 400 | args: unknown, 401 | apiClient: AxiosInstance, 402 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 403 | ) { 404 | if (!isUpdateEmailWarmupParams(args)) { 405 | throw new McpError( 406 | ErrorCode.InvalidParams, 407 | 'Invalid arguments for smartlead_update_email_warmup' 408 | ); 409 | } 410 | 411 | const { email_account_id, ...warmupParams } = args as UpdateEmailWarmupParams; 412 | 413 | // API endpoint: https://server.smartlead.ai/api/v1/email-accounts/{email_account_id}/warmup 414 | try { 415 | const response = await withRetry( 416 | async () => apiClient.post(`/email-accounts/${email_account_id}/warmup`, warmupParams), 417 | 'update email warmup' 418 | ); 419 | 420 | return { 421 | content: [ 422 | { 423 | type: 'text', 424 | text: JSON.stringify(response.data, null, 2), 425 | }, 426 | ], 427 | isError: false, 428 | }; 429 | } catch (error: any) { 430 | return { 431 | content: [{ 432 | type: 'text', 433 | text: `API Error: ${error.response?.data?.message || error.message}` 434 | }], 435 | isError: true, 436 | }; 437 | } 438 | } 439 | 440 | async function handleReconnectEmailAccount( 441 | args: unknown, 442 | apiClient: AxiosInstance, 443 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 444 | ) { 445 | if (!isReconnectEmailAccountParams(args)) { 446 | throw new McpError( 447 | ErrorCode.InvalidParams, 448 | 'Invalid arguments for smartlead_reconnect_email_account' 449 | ); 450 | } 451 | 452 | // API endpoint: https://server.smartlead.ai/api/v1/email-accounts/reconnect-failed-email-accounts 453 | try { 454 | const response = await withRetry( 455 | async () => apiClient.post('/email-accounts/reconnect-failed-email-accounts', {}), 456 | 'reconnect failed email accounts' 457 | ); 458 | 459 | return { 460 | content: [ 461 | { 462 | type: 'text', 463 | text: JSON.stringify(response.data, null, 2), 464 | }, 465 | ], 466 | isError: false, 467 | }; 468 | } catch (error: any) { 469 | return { 470 | content: [{ 471 | type: 'text', 472 | text: `API Error: ${error.response?.data?.message || error.message}` 473 | }], 474 | isError: true, 475 | }; 476 | } 477 | } 478 | 479 | async function handleUpdateEmailAccountTag( 480 | args: unknown, 481 | apiClient: AxiosInstance, 482 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 483 | ) { 484 | if (!isUpdateEmailAccountTagParams(args)) { 485 | throw new McpError( 486 | ErrorCode.InvalidParams, 487 | 'Invalid arguments for smartlead_update_email_account_tag' 488 | ); 489 | } 490 | 491 | const { id, name, color } = args as UpdateEmailAccountTagParams; 492 | 493 | // API endpoint: https://server.smartlead.ai/api/v1/email-accounts/tag-manager 494 | try { 495 | const response = await withRetry( 496 | async () => apiClient.post('/email-accounts/tag-manager', { 497 | id, 498 | name, 499 | color 500 | }), 501 | 'update email account tag' 502 | ); 503 | 504 | return { 505 | content: [ 506 | { 507 | type: 'text', 508 | text: JSON.stringify(response.data, null, 2), 509 | }, 510 | ], 511 | isError: false, 512 | }; 513 | } catch (error: any) { 514 | return { 515 | content: [{ 516 | type: 'text', 517 | text: `API Error: ${error.response?.data?.message || error.message}` 518 | }], 519 | isError: true, 520 | }; 521 | } 522 | } ``` -------------------------------------------------------------------------------- /src/handlers/statistics.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { AxiosInstance } from 'axios'; 2 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; 3 | import { 4 | isCampaignStatisticsParams, 5 | isCampaignStatisticsByDateParams, 6 | isWarmupStatsByEmailParams, 7 | isCampaignTopLevelAnalyticsParams, 8 | isCampaignTopLevelAnalyticsByDateParams, 9 | isCampaignLeadStatisticsParams, 10 | isCampaignMailboxStatisticsParams, 11 | isDownloadCampaignDataParams, 12 | isViewDownloadStatisticsParams 13 | } from '../types/statistics.js'; 14 | import { trackDownload, getDownloadStats, getDownloadRecords } from '../utils/download-tracker.js'; 15 | 16 | // Handler for statistics-related tools 17 | export async function handleStatisticsTool( 18 | toolName: string, 19 | args: unknown, 20 | apiClient: AxiosInstance, 21 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 22 | ) { 23 | switch (toolName) { 24 | case 'smartlead_get_campaign_statistics': { 25 | return handleCampaignStatistics(args, apiClient, withRetry); 26 | } 27 | case 'smartlead_get_campaign_statistics_by_date': { 28 | return handleCampaignStatisticsByDate(args, apiClient, withRetry); 29 | } 30 | case 'smartlead_get_warmup_stats_by_email': { 31 | return handleWarmupStatsByEmail(args, apiClient, withRetry); 32 | } 33 | case 'smartlead_get_campaign_top_level_analytics': { 34 | return handleCampaignTopLevelAnalytics(args, apiClient, withRetry); 35 | } 36 | case 'smartlead_get_campaign_top_level_analytics_by_date': { 37 | return handleCampaignTopLevelAnalyticsByDate(args, apiClient, withRetry); 38 | } 39 | case 'smartlead_get_campaign_lead_statistics': { 40 | return handleCampaignLeadStatistics(args, apiClient, withRetry); 41 | } 42 | case 'smartlead_get_campaign_mailbox_statistics': { 43 | return handleCampaignMailboxStatistics(args, apiClient, withRetry); 44 | } 45 | case 'smartlead_download_campaign_data': { 46 | return handleDownloadCampaignData(args, apiClient, withRetry); 47 | } 48 | case 'smartlead_view_download_statistics': { 49 | return handleViewDownloadStatistics(args); 50 | } 51 | default: 52 | throw new Error(`Unknown statistics tool: ${toolName}`); 53 | } 54 | } 55 | 56 | // Individual handlers for each tool 57 | async function handleCampaignStatistics( 58 | args: unknown, 59 | apiClient: AxiosInstance, 60 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 61 | ) { 62 | if (!isCampaignStatisticsParams(args)) { 63 | throw new McpError( 64 | ErrorCode.InvalidParams, 65 | 'Invalid arguments for smartlead_get_campaign_statistics' 66 | ); 67 | } 68 | 69 | const { campaign_id, ...queryParams } = args; 70 | 71 | try { 72 | const response = await withRetry( 73 | async () => apiClient.get(`/campaigns/${campaign_id}/statistics`, { params: queryParams }), 74 | 'get campaign statistics' 75 | ); 76 | 77 | return { 78 | content: [ 79 | { 80 | type: 'text', 81 | text: JSON.stringify(response.data, null, 2), 82 | }, 83 | ], 84 | isError: false, 85 | }; 86 | } catch (error: any) { 87 | return { 88 | content: [{ 89 | type: 'text', 90 | text: `API Error: ${error.response?.data?.message || error.message}` 91 | }], 92 | isError: true, 93 | }; 94 | } 95 | } 96 | 97 | async function handleCampaignStatisticsByDate( 98 | args: unknown, 99 | apiClient: AxiosInstance, 100 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 101 | ) { 102 | if (!isCampaignStatisticsByDateParams(args)) { 103 | throw new McpError( 104 | ErrorCode.InvalidParams, 105 | 'Invalid arguments for smartlead_get_campaign_statistics_by_date' 106 | ); 107 | } 108 | 109 | const { campaign_id, start_date, end_date } = args; 110 | 111 | try { 112 | const response = await withRetry( 113 | async () => apiClient.get(`/campaigns/${campaign_id}/analytics-by-date`, { 114 | params: { 115 | start_date, 116 | end_date 117 | } 118 | }), 119 | 'get campaign statistics by date' 120 | ); 121 | 122 | return { 123 | content: [ 124 | { 125 | type: 'text', 126 | text: JSON.stringify(response.data, null, 2), 127 | }, 128 | ], 129 | isError: false, 130 | }; 131 | } catch (error: any) { 132 | return { 133 | content: [{ 134 | type: 'text', 135 | text: `API Error: ${error.response?.data?.message || error.message}` 136 | }], 137 | isError: true, 138 | }; 139 | } 140 | } 141 | 142 | async function handleWarmupStatsByEmail( 143 | args: unknown, 144 | apiClient: AxiosInstance, 145 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 146 | ) { 147 | if (!isWarmupStatsByEmailParams(args)) { 148 | throw new McpError( 149 | ErrorCode.InvalidParams, 150 | 'Invalid arguments for smartlead_get_warmup_stats_by_email' 151 | ); 152 | } 153 | 154 | const { email_account_id } = args; 155 | 156 | try { 157 | const response = await withRetry( 158 | async () => apiClient.get(`/email-accounts/${email_account_id}/warmup-stats`), 159 | 'get warmup stats by email' 160 | ); 161 | 162 | return { 163 | content: [ 164 | { 165 | type: 'text', 166 | text: JSON.stringify(response.data, null, 2), 167 | }, 168 | ], 169 | isError: false, 170 | }; 171 | } catch (error: any) { 172 | return { 173 | content: [{ 174 | type: 'text', 175 | text: `API Error: ${error.response?.data?.message || error.message}` 176 | }], 177 | isError: true, 178 | }; 179 | } 180 | } 181 | 182 | async function handleCampaignTopLevelAnalytics( 183 | args: unknown, 184 | apiClient: AxiosInstance, 185 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 186 | ) { 187 | if (!isCampaignTopLevelAnalyticsParams(args)) { 188 | throw new McpError( 189 | ErrorCode.InvalidParams, 190 | 'Invalid arguments for smartlead_get_campaign_top_level_analytics' 191 | ); 192 | } 193 | 194 | const { campaign_id } = args; 195 | 196 | try { 197 | const response = await withRetry( 198 | async () => apiClient.get(`/campaigns/${campaign_id}/analytics`), 199 | 'get campaign top level analytics' 200 | ); 201 | 202 | return { 203 | content: [ 204 | { 205 | type: 'text', 206 | text: JSON.stringify(response.data, null, 2), 207 | }, 208 | ], 209 | isError: false, 210 | }; 211 | } catch (error: any) { 212 | return { 213 | content: [{ 214 | type: 'text', 215 | text: `API Error: ${error.response?.data?.message || error.message}` 216 | }], 217 | isError: true, 218 | }; 219 | } 220 | } 221 | 222 | async function handleCampaignTopLevelAnalyticsByDate( 223 | args: unknown, 224 | apiClient: AxiosInstance, 225 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 226 | ) { 227 | if (!isCampaignTopLevelAnalyticsByDateParams(args)) { 228 | throw new McpError( 229 | ErrorCode.InvalidParams, 230 | 'Invalid arguments for smartlead_get_campaign_top_level_analytics_by_date' 231 | ); 232 | } 233 | 234 | const { campaign_id, start_date, end_date } = args; 235 | 236 | try { 237 | const response = await withRetry( 238 | async () => apiClient.get(`/campaigns/${campaign_id}/top-level-analytics-by-date`, { 239 | params: { 240 | start_date, 241 | end_date 242 | } 243 | }), 244 | 'get campaign top level analytics by date' 245 | ); 246 | 247 | return { 248 | content: [ 249 | { 250 | type: 'text', 251 | text: JSON.stringify(response.data, null, 2), 252 | }, 253 | ], 254 | isError: false, 255 | }; 256 | } catch (error: any) { 257 | return { 258 | content: [{ 259 | type: 'text', 260 | text: `API Error: ${error.response?.data?.message || error.message}` 261 | }], 262 | isError: true, 263 | }; 264 | } 265 | } 266 | 267 | async function handleCampaignLeadStatistics( 268 | args: unknown, 269 | apiClient: AxiosInstance, 270 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 271 | ) { 272 | if (!isCampaignLeadStatisticsParams(args)) { 273 | throw new McpError( 274 | ErrorCode.InvalidParams, 275 | 'Invalid arguments for smartlead_get_campaign_lead_statistics' 276 | ); 277 | } 278 | 279 | const { campaign_id, ...queryParams } = args; 280 | 281 | try { 282 | const response = await withRetry( 283 | async () => apiClient.get(`/campaigns/${campaign_id}/lead-statistics`, { 284 | params: queryParams 285 | }), 286 | 'get campaign lead statistics' 287 | ); 288 | 289 | return { 290 | content: [ 291 | { 292 | type: 'text', 293 | text: JSON.stringify(response.data, null, 2), 294 | }, 295 | ], 296 | isError: false, 297 | }; 298 | } catch (error: any) { 299 | return { 300 | content: [{ 301 | type: 'text', 302 | text: `API Error: ${error.response?.data?.message || error.message}` 303 | }], 304 | isError: true, 305 | }; 306 | } 307 | } 308 | 309 | async function handleCampaignMailboxStatistics( 310 | args: unknown, 311 | apiClient: AxiosInstance, 312 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 313 | ) { 314 | if (!isCampaignMailboxStatisticsParams(args)) { 315 | throw new McpError( 316 | ErrorCode.InvalidParams, 317 | 'Invalid arguments for smartlead_get_campaign_mailbox_statistics' 318 | ); 319 | } 320 | 321 | const { campaign_id, ...queryParams } = args; 322 | 323 | try { 324 | const response = await withRetry( 325 | async () => apiClient.get(`/campaigns/${campaign_id}/mailbox-statistics`, { 326 | params: queryParams 327 | }), 328 | 'get campaign mailbox statistics' 329 | ); 330 | 331 | return { 332 | content: [ 333 | { 334 | type: 'text', 335 | text: JSON.stringify(response.data, null, 2), 336 | }, 337 | ], 338 | isError: false, 339 | }; 340 | } catch (error: any) { 341 | return { 342 | content: [{ 343 | type: 'text', 344 | text: `API Error: ${error.response?.data?.message || error.message}` 345 | }], 346 | isError: true, 347 | }; 348 | } 349 | } 350 | 351 | // New function to handle the download and tracking 352 | async function handleDownloadCampaignData( 353 | args: unknown, 354 | apiClient: AxiosInstance, 355 | withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> 356 | ) { 357 | if (!isDownloadCampaignDataParams(args)) { 358 | throw new McpError( 359 | ErrorCode.InvalidParams, 360 | 'Invalid arguments for smartlead_download_campaign_data' 361 | ); 362 | } 363 | 364 | const { campaign_id, download_type, format, user_id } = args; 365 | 366 | try { 367 | let endpoint = ''; 368 | let transformFn = (data: any) => data; 369 | 370 | // Determine the API endpoint based on download type 371 | switch (download_type) { 372 | case 'analytics': 373 | endpoint = `/campaigns/${campaign_id}/analytics`; 374 | break; 375 | case 'leads': 376 | endpoint = `/campaigns/${campaign_id}/leads`; 377 | break; 378 | case 'sequence': 379 | endpoint = `/campaigns/${campaign_id}/sequence`; 380 | break; 381 | case 'full_export': 382 | endpoint = `/campaigns/${campaign_id}/export`; 383 | break; 384 | default: 385 | throw new McpError( 386 | ErrorCode.InvalidParams, 387 | `Invalid download_type: ${download_type}` 388 | ); 389 | } 390 | 391 | // Fetch the data 392 | const response = await withRetry( 393 | async () => apiClient.get(endpoint), 394 | `download campaign ${download_type}` 395 | ); 396 | 397 | let responseData = response.data; 398 | 399 | // If format is CSV, convert the JSON data to CSV 400 | if (format === 'csv') { 401 | responseData = convertToCSV(responseData); 402 | } 403 | 404 | // Track the download 405 | const downloadId = trackDownload( 406 | campaign_id, 407 | download_type, 408 | format, 409 | user_id 410 | ); 411 | 412 | // Add download metadata to the response 413 | const result = { 414 | data: responseData, 415 | download_metadata: { 416 | download_id: downloadId, 417 | timestamp: new Date().toISOString(), 418 | campaign_id, 419 | download_type, 420 | format 421 | } 422 | }; 423 | 424 | return { 425 | content: [ 426 | { 427 | type: 'text', 428 | text: format === 'json' 429 | ? JSON.stringify(result, null, 2) 430 | : result.data, // CSV data is already stringified 431 | }, 432 | ], 433 | isError: false, 434 | }; 435 | } catch (error: any) { 436 | return { 437 | content: [{ 438 | type: 'text', 439 | text: `API Error: ${error.response?.data?.message || error.message}` 440 | }], 441 | isError: true, 442 | }; 443 | } 444 | } 445 | 446 | // Helper function to convert JSON to CSV 447 | function convertToCSV(data: any): string { 448 | if (!data || typeof data !== 'object') { 449 | return ''; 450 | } 451 | 452 | // Handle array of objects 453 | if (Array.isArray(data)) { 454 | if (data.length === 0) return ''; 455 | 456 | // Get all possible headers from all objects 457 | const headers = new Set<string>(); 458 | data.forEach(item => { 459 | if (item && typeof item === 'object') { 460 | Object.keys(item).forEach(key => headers.add(key)); 461 | } 462 | }); 463 | 464 | const headerRow = Array.from(headers).join(','); 465 | const rows = data.map(item => { 466 | if (!item || typeof item !== 'object') return ''; 467 | return Array.from(headers) 468 | .map(header => { 469 | const cell = item[header] ?? ''; 470 | // Escape strings with quotes if they contain commas 471 | return typeof cell === 'string' && cell.includes(',') 472 | ? `"${cell.replace(/"/g, '""')}"` 473 | : String(cell); 474 | }) 475 | .join(','); 476 | }); 477 | 478 | return [headerRow, ...rows].join('\n'); 479 | } 480 | 481 | // Handle single object 482 | const headers = Object.keys(data).join(','); 483 | const values = Object.values(data) 484 | .map(val => { 485 | if (typeof val === 'string' && val.includes(',')) { 486 | return `"${val.replace(/"/g, '""')}"`; 487 | } 488 | return String(val); 489 | }) 490 | .join(','); 491 | 492 | return [headers, values].join('\n'); 493 | } 494 | 495 | // New function to handle viewing download statistics 496 | async function handleViewDownloadStatistics(args: unknown) { 497 | if (!isViewDownloadStatisticsParams(args)) { 498 | throw new McpError( 499 | ErrorCode.InvalidParams, 500 | 'Invalid arguments for smartlead_view_download_statistics' 501 | ); 502 | } 503 | 504 | const { time_period = 'all', group_by = 'type' } = args; 505 | 506 | try { 507 | // Get all download records 508 | const allRecords = getDownloadRecords(); 509 | 510 | // Filter records by time period 511 | const now = new Date(); 512 | const filteredRecords = allRecords.filter(record => { 513 | const recordDate = new Date(record.timestamp); 514 | 515 | switch (time_period) { 516 | case 'today': 517 | return recordDate.toDateString() === now.toDateString(); 518 | case 'week': { 519 | const oneWeekAgo = new Date(); 520 | oneWeekAgo.setDate(now.getDate() - 7); 521 | return recordDate >= oneWeekAgo; 522 | } 523 | case 'month': { 524 | const oneMonthAgo = new Date(); 525 | oneMonthAgo.setMonth(now.getMonth() - 1); 526 | return recordDate >= oneMonthAgo; 527 | } 528 | case 'all': 529 | default: 530 | return true; 531 | } 532 | }); 533 | 534 | // Get basic statistics 535 | const stats = { 536 | totalDownloads: filteredRecords.length, 537 | timePeriod: time_period, 538 | uniqueUsers: new Set(filteredRecords.map(r => r.userId || r.machineId)).size 539 | }; 540 | 541 | // Group by specified field 542 | let groupedData: Record<string, number> = {}; 543 | 544 | switch (group_by) { 545 | case 'type': 546 | groupedData = filteredRecords.reduce((acc, record) => { 547 | acc[record.downloadType] = (acc[record.downloadType] || 0) + 1; 548 | return acc; 549 | }, {} as Record<string, number>); 550 | break; 551 | case 'format': 552 | groupedData = filteredRecords.reduce((acc, record) => { 553 | acc[record.format] = (acc[record.format] || 0) + 1; 554 | return acc; 555 | }, {} as Record<string, number>); 556 | break; 557 | case 'campaign': 558 | groupedData = filteredRecords.reduce((acc, record) => { 559 | const campaignKey = `campaign_${record.campaignId}`; 560 | acc[campaignKey] = (acc[campaignKey] || 0) + 1; 561 | return acc; 562 | }, {} as Record<string, number>); 563 | break; 564 | case 'date': 565 | groupedData = filteredRecords.reduce((acc, record) => { 566 | const date = record.timestamp.split('T')[0]; 567 | acc[date] = (acc[date] || 0) + 1; 568 | return acc; 569 | }, {} as Record<string, number>); 570 | break; 571 | } 572 | 573 | // Compile the result 574 | const result = { 575 | ...stats, 576 | groupedBy: group_by, 577 | groups: groupedData, 578 | // Include recent downloads for reference 579 | recentDownloads: filteredRecords 580 | .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) 581 | .slice(0, 5) 582 | }; 583 | 584 | return { 585 | content: [ 586 | { 587 | type: 'text', 588 | text: JSON.stringify(result, null, 2), 589 | }, 590 | ], 591 | isError: false, 592 | }; 593 | } catch (error: any) { 594 | return { 595 | content: [{ 596 | type: 'text', 597 | text: `Error retrieving download statistics: ${error.message}` 598 | }], 599 | isError: true, 600 | }; 601 | } 602 | } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { 5 | CallToolRequestSchema, 6 | ListToolsRequestSchema, 7 | InitializeRequestSchema, 8 | InitializedNotificationSchema, 9 | ServerCapabilities 10 | } from '@modelcontextprotocol/sdk/types.js'; 11 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 12 | import axios, { AxiosInstance } from 'axios'; 13 | import dotenv from 'dotenv'; 14 | import path from 'path'; 15 | 16 | // Import licensing system 17 | import { validateLicense, trackUsage, isFeatureEnabled, printLicenseStatus, getFeatureToken } from './licensing/index.js'; 18 | 19 | // Import Supergateway integration 20 | import { createSupergateway } from './supergateway.js'; 21 | 22 | // Import our modular components 23 | import { campaignTools } from './tools/campaign.js'; 24 | // import { emailTools } from './tools/email.js'; 25 | import { leadTools } from './tools/lead.js'; 26 | import { statisticsTools } from './tools/statistics.js'; 27 | import { smartDeliveryTools } from './tools/smartDelivery.js'; 28 | import { webhookTools } from './tools/webhooks.js'; 29 | import { clientManagementTools } from './tools/clientManagement.js'; 30 | import { smartSendersTools } from './tools/smartSenders.js'; 31 | import { handleCampaignTool } from './handlers/campaign.js'; 32 | // import { handleEmailTool } from './handlers/email.js'; 33 | import { handleLeadTool } from './handlers/lead.js'; 34 | import { handleStatisticsTool } from './handlers/statistics.js'; 35 | import { handleSmartDeliveryTool } from './handlers/smartDelivery.js'; 36 | import { handleWebhookTool } from './handlers/webhooks.js'; 37 | import { handleClientManagementTool } from './handlers/clientManagement.js'; 38 | import { handleSmartSendersTool } from './handlers/smartSenders.js'; 39 | import { enabledCategories, featureFlags } from './config/feature-config.js'; 40 | import { ToolCategory } from './types/common.js'; 41 | import { toolRegistry } from './registry/tool-registry.js'; 42 | 43 | console.log('Starting Smartlead MCP Server...'); 44 | 45 | // Load environment variables from .env file in the project root 46 | dotenv.config({ path: path.resolve(process.cwd(), '.env') }); 47 | 48 | // Check license on startup 49 | (async () => { 50 | // Print detailed license information 51 | await printLicenseStatus(); 52 | 53 | // Always enable n8n integration regardless of license 54 | featureFlags.n8nIntegration = true; 55 | })().catch(error => { 56 | console.error('License validation error:', error); 57 | // Still ensure n8n integration is enabled even if there's an error 58 | featureFlags.n8nIntegration = true; 59 | }); 60 | 61 | // Check if Supergateway integration is enabled 62 | const useSupergateway = process.env.USE_SUPERGATEWAY === 'true'; 63 | 64 | // Define server capabilities 65 | const serverCapabilities: ServerCapabilities = { 66 | tools: { 67 | callTool: true, 68 | listTools: true 69 | }, 70 | logging: { 71 | loggingMessage: true 72 | } 73 | }; 74 | 75 | // Server implementation 76 | const server = new Server( 77 | { 78 | name: 'smartlead-mcp', 79 | version: '1.0.0', 80 | }, 81 | { 82 | capabilities: serverCapabilities, 83 | instructions: 'Smartlead MCP Server for accessing Smartlead API functionality' 84 | } 85 | ); 86 | 87 | // Get API key and URL from environment variables 88 | const SMARTLEAD_API_KEY = process.env.SMARTLEAD_API_KEY; 89 | const SMARTLEAD_API_URL = process.env.SMARTLEAD_API_URL || 'https://server.smartlead.ai/api/v1'; 90 | 91 | // Check if API key is provided 92 | if (!SMARTLEAD_API_KEY) { 93 | console.error('Error: SMARTLEAD_API_KEY environment variable is required'); 94 | process.exit(1); 95 | } 96 | 97 | // Configuration for retries and monitoring 98 | const CONFIG = { 99 | retry: { 100 | maxAttempts: Number(process.env.SMARTLEAD_RETRY_MAX_ATTEMPTS) || 3, 101 | initialDelay: Number(process.env.SMARTLEAD_RETRY_INITIAL_DELAY) || 1000, 102 | maxDelay: Number(process.env.SMARTLEAD_RETRY_MAX_DELAY) || 10000, 103 | backoffFactor: Number(process.env.SMARTLEAD_RETRY_BACKOFF_FACTOR) || 2, 104 | }, 105 | }; 106 | 107 | // Initialize Axios instance for API requests 108 | const apiClient: AxiosInstance = axios.create({ 109 | baseURL: SMARTLEAD_API_URL, 110 | params: { 111 | api_key: SMARTLEAD_API_KEY, 112 | }, 113 | headers: { 114 | 'Content-Type': 'application/json', 115 | }, 116 | }); 117 | 118 | let isStdioTransport = true; 119 | 120 | function safeLog( 121 | level: 122 | | 'error' 123 | | 'debug' 124 | | 'info' 125 | | 'notice' 126 | | 'warning' 127 | | 'critical' 128 | | 'alert' 129 | | 'emergency', 130 | data: any 131 | ): void { 132 | try { 133 | // Always log to stderr for now to avoid protocol interference 134 | const logMessage = typeof data === 'object' ? JSON.stringify(data) : data; 135 | console.error(`[${level}] ${logMessage}`); 136 | 137 | // Try to send via proper logging mechanism, but don't throw if it fails 138 | try { 139 | server.sendLoggingMessage({ level, data }).catch(e => { 140 | console.error(`Failed to send log via protocol: ${e.message}`); 141 | }); 142 | } catch (e) { 143 | console.error(`Error in logging mechanism: ${e instanceof Error ? e.message : String(e)}`); 144 | } 145 | } catch (e) { 146 | // Last resort fallback if anything in the logging fails 147 | console.error(`[${level}] Failed to format log message: ${e instanceof Error ? e.message : String(e)}`); 148 | try { 149 | console.error(`Original data type: ${typeof data}`); 150 | } catch (_) { 151 | // Ignore any errors in the fallback logging 152 | } 153 | } 154 | } 155 | 156 | // Add utility function for delay 157 | function delay(ms: number): Promise<void> { 158 | return new Promise((resolve) => setTimeout(resolve, ms)); 159 | } 160 | 161 | // Add retry logic with exponential backoff 162 | async function withRetry<T>( 163 | operation: () => Promise<T>, 164 | context: string, 165 | attempt = 1 166 | ): Promise<T> { 167 | try { 168 | return await operation(); 169 | } catch (error) { 170 | const isRateLimit = 171 | error instanceof Error && 172 | (error.message.includes('rate limit') || error.message.includes('429')); 173 | 174 | if (isRateLimit && attempt < CONFIG.retry.maxAttempts) { 175 | const delayMs = Math.min( 176 | CONFIG.retry.initialDelay * 177 | Math.pow(CONFIG.retry.backoffFactor, attempt - 1), 178 | CONFIG.retry.maxDelay 179 | ); 180 | 181 | safeLog( 182 | 'warning', 183 | `Rate limit hit for ${context}. Attempt ${attempt}/${CONFIG.retry.maxAttempts}. Retrying in ${delayMs}ms` 184 | ); 185 | 186 | await delay(delayMs); 187 | return withRetry(operation, context, attempt + 1); 188 | } 189 | 190 | throw error; 191 | } 192 | } 193 | 194 | // Register all available tools with the registry 195 | function registerTools() { 196 | // Register campaign tools if enabled 197 | if (enabledCategories.campaignManagement) { 198 | toolRegistry.registerMany(campaignTools); 199 | } 200 | 201 | // Register email account tools if enabled 202 | // if (enabledCategories.emailAccountManagement) { 203 | // toolRegistry.registerMany(emailTools); 204 | // } 205 | 206 | // Register lead management tools if enabled 207 | if (enabledCategories.leadManagement) { 208 | toolRegistry.registerMany(leadTools); 209 | } 210 | 211 | // Register campaign statistics tools if enabled 212 | if (enabledCategories.campaignStatistics) { 213 | toolRegistry.registerMany(statisticsTools); 214 | } 215 | 216 | // Register smart delivery tools if enabled 217 | if (enabledCategories.smartDelivery) { 218 | toolRegistry.registerMany(smartDeliveryTools); 219 | } 220 | 221 | // Register webhook tools if enabled 222 | if (enabledCategories.webhooks) { 223 | toolRegistry.registerMany(webhookTools); 224 | } 225 | 226 | // Register client management tools if enabled 227 | if (enabledCategories.clientManagement) { 228 | toolRegistry.registerMany(clientManagementTools); 229 | } 230 | 231 | // Register smart senders tools if enabled 232 | if (enabledCategories.smartSenders) { 233 | toolRegistry.registerMany(smartSendersTools); 234 | } 235 | 236 | // Add more categories here as they are implemented 237 | // For example: 238 | // if (enabledCategories.emailAccountManagement) { 239 | // toolRegistry.registerMany(emailAccountTools); 240 | // } 241 | } 242 | 243 | // Initialize the tool registry 244 | registerTools(); 245 | 246 | // Tool handlers 247 | server.setRequestHandler(ListToolsRequestSchema, async () => { 248 | safeLog('info', 'Handling listTools request'); 249 | 250 | try { 251 | // Get license-filtered tools 252 | const tools = await toolRegistry.getEnabledToolsAsync(); 253 | 254 | // Log license status and available tools count 255 | const license = await validateLicense(); 256 | safeLog('info', `Listing ${tools.length} tools available in ${license.level} license tier`); 257 | 258 | return { 259 | tools: tools, 260 | }; 261 | } catch (error) { 262 | // Fallback to sync method if async fails 263 | safeLog('warning', `License validation failed, using default tool list: ${error}`); 264 | return { 265 | tools: toolRegistry.getEnabledTools(), 266 | }; 267 | } 268 | }); 269 | 270 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 271 | const startTime = Date.now(); 272 | try { 273 | const { name, arguments: args } = request.params; 274 | 275 | // Log incoming request with timestamp 276 | safeLog( 277 | 'info', 278 | `[${new Date().toISOString()}] Received request for tool: ${name}` 279 | ); 280 | 281 | // Safe guard for undefined arguments 282 | const toolArgs = args || {}; 283 | 284 | // Check if the tool exists in the registry 285 | if (!toolRegistry.hasToolWithName(name)) { 286 | return { 287 | content: [{ type: "text", text: `Unknown tool: ${name}` }], 288 | isError: true, 289 | }; 290 | } 291 | 292 | // Get the tool details to determine which handler to use 293 | const tool = toolRegistry.getByName(name); 294 | 295 | if (!tool) { 296 | return { 297 | content: [{ type: "text", text: `Tool ${name} not found in registry` }], 298 | isError: true, 299 | }; 300 | } 301 | 302 | // Check license for tool access 303 | const licenseResult = await validateLicense(); 304 | 305 | // For premium features, we can require server-side validation tokens 306 | if (tool.requiresServerValidation && tool.category === 'premium') { 307 | const token = await getFeatureToken(); 308 | if (!token) { 309 | return { 310 | content: [{ 311 | type: "text", 312 | text: `Tool ${name} requires server-side validation. Please ensure you have a valid Premium license.` 313 | }], 314 | isError: true, 315 | }; 316 | } 317 | } 318 | 319 | // Track usage for analytics and quota tracking 320 | await trackUsage(process.env.JEAN_LICENSE_KEY, name); 321 | 322 | // Check if this tool category is allowed by the license 323 | if (!licenseResult.features.allowedCategories.includes(tool.category)) { 324 | return { 325 | content: [{ 326 | type: "text", 327 | text: `Tool ${name} is not available in your current license tier (${licenseResult.level}). Please upgrade to access this feature.` 328 | }], 329 | isError: true, 330 | }; 331 | } 332 | 333 | // Check for usage quota limits 334 | if (licenseResult.usageCount >= licenseResult.features.maxRequests && licenseResult.level !== 'premium') { 335 | return { 336 | content: [{ 337 | type: "text", 338 | text: `You have reached your monthly usage quota (${licenseResult.features.maxRequests} requests). Please upgrade your plan to continue using this service.` 339 | }], 340 | isError: true, 341 | }; 342 | } 343 | 344 | // Call the appropriate handler based on tool category 345 | switch (tool.category) { 346 | case ToolCategory.CAMPAIGN_MANAGEMENT: 347 | return await handleCampaignTool(name, toolArgs, apiClient, withRetry); 348 | // case ToolCategory.EMAIL_ACCOUNT_MANAGEMENT: 349 | // return await handleEmailTool(name, toolArgs, apiClient, withRetry); 350 | case ToolCategory.LEAD_MANAGEMENT: 351 | return await handleLeadTool(name, toolArgs, apiClient, withRetry); 352 | case ToolCategory.CAMPAIGN_STATISTICS: 353 | return await handleStatisticsTool(name, toolArgs, apiClient, withRetry); 354 | case ToolCategory.SMART_DELIVERY: 355 | return await handleSmartDeliveryTool(name, toolArgs, apiClient, withRetry); 356 | case ToolCategory.WEBHOOKS: 357 | return await handleWebhookTool(name, toolArgs, apiClient, withRetry); 358 | case ToolCategory.CLIENT_MANAGEMENT: 359 | return await handleClientManagementTool(name, toolArgs, apiClient, withRetry); 360 | case ToolCategory.SMART_SENDERS: 361 | return await handleSmartSendersTool(name, toolArgs, apiClient, withRetry); 362 | default: 363 | return { 364 | content: [{ type: "text", text: `Unsupported tool category: ${tool.category}` }], 365 | isError: true, 366 | }; 367 | } 368 | } catch (error) { 369 | // Log detailed error information 370 | safeLog('error', { 371 | message: `Request failed: ${ 372 | error instanceof Error ? error.message : String(error) 373 | }`, 374 | tool: request.params.name, 375 | arguments: request.params.arguments, 376 | timestamp: new Date().toISOString(), 377 | duration: Date.now() - startTime, 378 | }); 379 | return { 380 | content: [ 381 | { 382 | type: "text", 383 | text: `Error: ${error instanceof Error ? error.message : String(error)}`, 384 | }, 385 | ], 386 | isError: true, 387 | }; 388 | } finally { 389 | // Log request completion with performance metrics 390 | safeLog('info', `Request completed in ${Date.now() - startTime}ms`); 391 | } 392 | }); 393 | 394 | // Initialize handler (part of the protocol) 395 | server.setRequestHandler(InitializeRequestSchema, async (request) => { 396 | safeLog('info', `Handling initialize request from ${request.params.clientInfo?.name || 'unknown client'}`); 397 | console.error(`[DEBUG] Initialize request received: ${JSON.stringify(request.params, null, 2)}`); 398 | 399 | // Respond with our server info and capabilities 400 | const response = { 401 | serverInfo: { 402 | name: 'smartlead-mcp', 403 | version: '1.0.0', 404 | }, 405 | capabilities: serverCapabilities, 406 | instructions: 'Smartlead MCP Server for accessing Smartlead API functionality', 407 | protocolVersion: request.params.protocolVersion || '2024-11-05' 408 | }; 409 | 410 | console.error(`[DEBUG] Sending initialize response: ${JSON.stringify(response, null, 2)}`); 411 | return response; 412 | }); 413 | 414 | // Initialized notification (part of the protocol) 415 | server.setNotificationHandler(InitializedNotificationSchema, () => { 416 | safeLog('info', 'Client initialized - ready to handle requests'); 417 | console.error('[DEBUG] Received initialized notification from client'); 418 | }); 419 | 420 | // Server startup 421 | async function runServer() { 422 | try { 423 | console.error('Initializing Smartlead MCP Server...'); 424 | 425 | // Check if we're trying to use n8n integration 426 | const usingN8nMode = process.env.USE_SUPERGATEWAY === 'true' || process.argv.includes('--sse'); 427 | 428 | if (usingN8nMode) { 429 | // Check license for n8n integration permission 430 | const licenseResult = await validateLicense(); 431 | 432 | if (!licenseResult.features.n8nIntegration) { 433 | console.error('============================================================='); 434 | console.error('ERROR: Your license does not include n8n integration features'); 435 | console.error('This feature requires a Basic or Premium license subscription.'); 436 | console.error('Visit https://yourservice.com/pricing to upgrade your plan.'); 437 | console.error('============================================================='); 438 | process.exit(1); 439 | } else { 440 | console.error('n8n integration enabled - ' + licenseResult.level.charAt(0).toUpperCase() + licenseResult.level.slice(1) + ' license detected'); 441 | } 442 | } 443 | 444 | // Use standard stdio transport directly 445 | const transport = new StdioServerTransport(); 446 | 447 | console.error('Running in stdio mode, logging will be directed to stderr'); 448 | 449 | // Set up error handling 450 | process.on('uncaughtException', (error) => { 451 | console.error(`[FATAL] Uncaught exception: ${error.message}`); 452 | console.error(error.stack); 453 | // Don't exit - just log the error 454 | }); 455 | 456 | process.on('unhandledRejection', (reason, promise) => { 457 | console.error(`[FATAL] Unhandled promise rejection: ${reason}`); 458 | // Don't exit - just log the error 459 | }); 460 | 461 | // Add transport error handler 462 | transport.onerror = (error) => { 463 | console.error(`[ERROR] Transport error: ${error.message}`); 464 | }; 465 | 466 | // Connect to the transport 467 | await server.connect(transport); 468 | 469 | // Set onclose handler 470 | transport.onclose = () => { 471 | console.error('[INFO] Transport was closed. This should only happen when the process is shutting down.'); 472 | }; 473 | 474 | // Now that we're connected, we can send logging messages 475 | safeLog('info', 'Smartlead MCP Server initialized successfully'); 476 | safeLog( 477 | 'info', 478 | `Configuration: API URL: ${SMARTLEAD_API_URL}` 479 | ); 480 | 481 | // Log license information 482 | const licenseInfo = await validateLicense(); 483 | safeLog('info', `License tier: ${licenseInfo.level} - ${licenseInfo.message}`); 484 | 485 | // Log which categories are enabled 486 | const enabledCats = licenseInfo.features.allowedCategories.join(', '); 487 | safeLog('info', `Enabled categories: ${enabledCats}`); 488 | 489 | // Log the number of enabled tools 490 | const enabledToolsCount = toolRegistry.getEnabledTools().length; 491 | safeLog('info', `Enabled tools: ${enabledToolsCount}`); 492 | 493 | console.error('Smartlead MCP Server running on stdio'); 494 | 495 | // Keep the process running 496 | process.stdin.resume(); 497 | } catch (error) { 498 | console.error('Fatal error running server:', error); 499 | process.exit(1); 500 | } 501 | } 502 | 503 | runServer().catch((error: any) => { 504 | console.error('Fatal error running server:', error); 505 | process.exit(1); 506 | }); ``` -------------------------------------------------------------------------------- /src/tools/smartDelivery.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { CategoryTool, ToolCategory } from '../types/common.js'; 2 | 3 | // Smart Delivery Tools 4 | 5 | export const GET_REGION_WISE_PROVIDERS_TOOL: CategoryTool = { 6 | name: 'smartlead_get_region_wise_providers', 7 | description: 'Retrieve the list of all Email Providers for spam testing classified by region/country. These provider IDs are required to create manual or automated spam tests.', 8 | category: ToolCategory.SMART_DELIVERY, 9 | inputSchema: { 10 | type: 'object', 11 | properties: { 12 | // This endpoint doesn't require any specific parameters beyond the API key 13 | // which is handled at the API client level 14 | }, 15 | required: [], 16 | }, 17 | }; 18 | 19 | export const CREATE_MANUAL_PLACEMENT_TEST_TOOL: CategoryTool = { 20 | name: 'smartlead_create_manual_placement_test', 21 | description: 'Create a manual placement test using Smartlead mailboxes to test email deliverability across various email providers.', 22 | category: ToolCategory.SMART_DELIVERY, 23 | inputSchema: { 24 | type: 'object', 25 | properties: { 26 | test_name: { 27 | type: 'string', 28 | description: 'Name of your test', 29 | }, 30 | description: { 31 | type: 'string', 32 | description: 'Description for your test to reference later', 33 | }, 34 | spam_filters: { 35 | type: 'array', 36 | items: { type: 'string' }, 37 | description: 'Array of spam filters to test across, e.g. ["spam_assassin"]', 38 | }, 39 | link_checker: { 40 | type: 'boolean', 41 | description: 'Enable to check if domains for links in email body are blacklisted', 42 | }, 43 | campaign_id: { 44 | type: 'integer', 45 | description: 'Campaign ID for which you want to select the sequence to test', 46 | }, 47 | sequence_mapping_id: { 48 | type: 'integer', 49 | description: 'The ID of the sequence or variant you would like to test', 50 | }, 51 | provider_ids: { 52 | type: 'array', 53 | items: { type: 'integer' }, 54 | description: 'Array of provider IDs to send test emails to', 55 | }, 56 | sender_accounts: { 57 | type: 'array', 58 | items: { type: 'string' }, 59 | description: 'Array of email addresses to use as senders', 60 | }, 61 | all_email_sent_without_time_gap: { 62 | type: 'boolean', 63 | description: 'Set true to send all emails simultaneously', 64 | }, 65 | min_time_btwn_emails: { 66 | type: 'integer', 67 | description: 'Time gap between each email from each mailbox (if time gap enabled)', 68 | }, 69 | min_time_unit: { 70 | type: 'string', 71 | description: 'Time unit for the time gap (minutes, hours, days)', 72 | }, 73 | is_warmup: { 74 | type: 'boolean', 75 | description: 'Set true to receive positive intent responses and move emails from spam to inbox', 76 | }, 77 | }, 78 | required: ['test_name', 'spam_filters', 'link_checker', 'campaign_id', 'sequence_mapping_id', 'provider_ids', 'sender_accounts', 'all_email_sent_without_time_gap', 'min_time_btwn_emails', 'min_time_unit', 'is_warmup'], 79 | }, 80 | }; 81 | 82 | export const CREATE_AUTOMATED_PLACEMENT_TEST_TOOL: CategoryTool = { 83 | name: 'smartlead_create_automated_placement_test', 84 | description: 'Create an automated placement test that runs on a schedule using Smart Delivery.', 85 | category: ToolCategory.SMART_DELIVERY, 86 | inputSchema: { 87 | type: 'object', 88 | properties: { 89 | test_name: { 90 | type: 'string', 91 | description: 'Name of your test', 92 | }, 93 | description: { 94 | type: 'string', 95 | description: 'Description for your test to reference later', 96 | }, 97 | spam_filters: { 98 | type: 'array', 99 | items: { type: 'string' }, 100 | description: 'Array of spam filters to test across, e.g. ["spam_assassin"]', 101 | }, 102 | link_checker: { 103 | type: 'boolean', 104 | description: 'Enable to check if domains for links in email body are blacklisted', 105 | }, 106 | campaign_id: { 107 | type: 'integer', 108 | description: 'Campaign ID for which you want to select the sequence to test', 109 | }, 110 | sequence_mapping_id: { 111 | type: 'integer', 112 | description: 'The ID of the sequence or variant you would like to test', 113 | }, 114 | provider_ids: { 115 | type: 'array', 116 | items: { type: 'integer' }, 117 | description: 'Array of provider IDs to send test emails to', 118 | }, 119 | sender_accounts: { 120 | type: 'array', 121 | items: { type: 'string' }, 122 | description: 'Array of email addresses to use as senders', 123 | }, 124 | all_email_sent_without_time_gap: { 125 | type: 'boolean', 126 | description: 'Set true to send all emails simultaneously', 127 | }, 128 | min_time_btwn_emails: { 129 | type: 'integer', 130 | description: 'Time gap between each email from each mailbox (if time gap enabled)', 131 | }, 132 | min_time_unit: { 133 | type: 'string', 134 | description: 'Time unit for the time gap (minutes, hours, days)', 135 | }, 136 | is_warmup: { 137 | type: 'boolean', 138 | description: 'Set true to receive positive intent responses and move emails from spam to inbox', 139 | }, 140 | schedule_start_time: { 141 | type: 'string', 142 | description: 'Start date and time to schedule or run the test (ISO format)', 143 | }, 144 | test_end_date: { 145 | type: 'string', 146 | description: 'End date to stop running your test (YYYY-MM-DD format)', 147 | }, 148 | every_days: { 149 | type: 'integer', 150 | description: 'Frequency of how often to run a new test', 151 | }, 152 | tz: { 153 | type: 'string', 154 | description: 'Timezone for scheduling', 155 | }, 156 | days: { 157 | type: 'array', 158 | items: { type: 'integer' }, 159 | description: 'Days of the week to run the test (1-7, where 1 is Monday)', 160 | }, 161 | starHour: { 162 | type: 'string', 163 | description: 'Test start time', 164 | }, 165 | folder_id: { 166 | type: 'integer', 167 | description: 'Folder ID to assign the test to', 168 | }, 169 | }, 170 | required: ['test_name', 'spam_filters', 'link_checker', 'campaign_id', 'sequence_mapping_id', 'provider_ids', 'sender_accounts', 'all_email_sent_without_time_gap', 'min_time_btwn_emails', 'min_time_unit', 'is_warmup', 'schedule_start_time', 'test_end_date', 'every_days', 'tz', 'days'], 171 | }, 172 | }; 173 | 174 | export const GET_SPAM_TEST_DETAILS_TOOL: CategoryTool = { 175 | name: 'smartlead_get_spam_test_details', 176 | description: 'Retrieve details of a specific spam test by ID.', 177 | category: ToolCategory.SMART_DELIVERY, 178 | inputSchema: { 179 | type: 'object', 180 | properties: { 181 | spam_test_id: { 182 | type: 'integer', 183 | description: 'ID of the spam test to retrieve details for', 184 | }, 185 | }, 186 | required: ['spam_test_id'], 187 | }, 188 | }; 189 | 190 | export const DELETE_SMART_DELIVERY_TESTS_TOOL: CategoryTool = { 191 | name: 'smartlead_delete_smart_delivery_tests', 192 | description: 'Delete multiple Smart Delivery tests in bulk.', 193 | category: ToolCategory.SMART_DELIVERY, 194 | inputSchema: { 195 | type: 'object', 196 | properties: { 197 | spamTestIds: { 198 | type: 'array', 199 | items: { type: 'integer' }, 200 | description: 'Array of spam test IDs to delete', 201 | }, 202 | }, 203 | required: ['spamTestIds'], 204 | }, 205 | }; 206 | 207 | export const STOP_AUTOMATED_TEST_TOOL: CategoryTool = { 208 | name: 'smartlead_stop_automated_test', 209 | description: 'Stop an active automated test before its end date.', 210 | category: ToolCategory.SMART_DELIVERY, 211 | inputSchema: { 212 | type: 'object', 213 | properties: { 214 | spam_test_id: { 215 | type: 'integer', 216 | description: 'ID of the automated test to stop', 217 | }, 218 | }, 219 | required: ['spam_test_id'], 220 | }, 221 | }; 222 | 223 | export const LIST_ALL_TESTS_TOOL: CategoryTool = { 224 | name: 'smartlead_list_all_tests', 225 | description: 'List all Smart Delivery tests, either manual or automated.', 226 | category: ToolCategory.SMART_DELIVERY, 227 | inputSchema: { 228 | type: 'object', 229 | properties: { 230 | testType: { 231 | type: 'string', 232 | enum: ['manual', 'auto'], 233 | description: 'Type of tests to list (manual or auto)', 234 | }, 235 | limit: { 236 | type: 'integer', 237 | description: 'Number of tests to retrieve (default: 10)', 238 | }, 239 | offset: { 240 | type: 'integer', 241 | description: 'Offset for pagination (default: 0)', 242 | }, 243 | }, 244 | required: ['testType'], 245 | }, 246 | }; 247 | 248 | export const GET_PROVIDER_WISE_REPORT_TOOL: CategoryTool = { 249 | name: 'smartlead_get_provider_wise_report', 250 | description: 'Get detailed report of a spam test sorted by email providers.', 251 | category: ToolCategory.SMART_DELIVERY, 252 | inputSchema: { 253 | type: 'object', 254 | properties: { 255 | spam_test_id: { 256 | type: 'integer', 257 | description: 'ID of the spam test to get the provider-wise report for', 258 | }, 259 | }, 260 | required: ['spam_test_id'], 261 | }, 262 | }; 263 | 264 | export const GET_GROUP_WISE_REPORT_TOOL: CategoryTool = { 265 | name: 'smartlead_get_group_wise_report', 266 | description: 'Get detailed report of a spam test sorted by location (region/country).', 267 | category: ToolCategory.SMART_DELIVERY, 268 | inputSchema: { 269 | type: 'object', 270 | properties: { 271 | spam_test_id: { 272 | type: 'integer', 273 | description: 'ID of the spam test to get the group-wise report for', 274 | }, 275 | }, 276 | required: ['spam_test_id'], 277 | }, 278 | }; 279 | 280 | export const GET_SENDER_ACCOUNT_WISE_REPORT_TOOL: CategoryTool = { 281 | name: 'smartlead_get_sender_account_wise_report', 282 | description: 'Get detailed report of a spam test sorted by sender accounts with details of each email from each mailbox.', 283 | category: ToolCategory.SMART_DELIVERY, 284 | inputSchema: { 285 | type: 'object', 286 | properties: { 287 | spam_test_id: { 288 | type: 'integer', 289 | description: 'ID of the spam test to get the sender account-wise report for', 290 | }, 291 | }, 292 | required: ['spam_test_id'], 293 | }, 294 | }; 295 | 296 | export const GET_SPAM_FILTER_DETAILS_TOOL: CategoryTool = { 297 | name: 'smartlead_get_spam_filter_details', 298 | description: 'Get spam filter report per sender mailbox showing each spam score with details leading to the score.', 299 | category: ToolCategory.SMART_DELIVERY, 300 | inputSchema: { 301 | type: 'object', 302 | properties: { 303 | spam_test_id: { 304 | type: 'integer', 305 | description: 'ID of the spam test to get the spam filter details for', 306 | }, 307 | }, 308 | required: ['spam_test_id'], 309 | }, 310 | }; 311 | 312 | export const GET_DKIM_DETAILS_TOOL: CategoryTool = { 313 | name: 'smartlead_get_dkim_details', 314 | description: 'Check if DKIM authentication passed or failed for each sender mailbox and receiver account.', 315 | category: ToolCategory.SMART_DELIVERY, 316 | inputSchema: { 317 | type: 'object', 318 | properties: { 319 | spam_test_id: { 320 | type: 'integer', 321 | description: 'ID of the spam test to get the DKIM details for', 322 | }, 323 | }, 324 | required: ['spam_test_id'], 325 | }, 326 | }; 327 | 328 | export const GET_SPF_DETAILS_TOOL: CategoryTool = { 329 | name: 'smartlead_get_spf_details', 330 | description: 'Check if SPF authentication passed or failed for the test.', 331 | category: ToolCategory.SMART_DELIVERY, 332 | inputSchema: { 333 | type: 'object', 334 | properties: { 335 | spam_test_id: { 336 | type: 'integer', 337 | description: 'ID of the spam test to get the SPF details for', 338 | }, 339 | }, 340 | required: ['spam_test_id'], 341 | }, 342 | }; 343 | 344 | export const GET_RDNS_DETAILS_TOOL: CategoryTool = { 345 | name: 'smartlead_get_rdns_details', 346 | description: 'Check if rDNS was correct for an IP sending the email.', 347 | category: ToolCategory.SMART_DELIVERY, 348 | inputSchema: { 349 | type: 'object', 350 | properties: { 351 | spam_test_id: { 352 | type: 'integer', 353 | description: 'ID of the spam test to get the rDNS details for', 354 | }, 355 | }, 356 | required: ['spam_test_id'], 357 | }, 358 | }; 359 | 360 | export const GET_SENDER_ACCOUNTS_TOOL: CategoryTool = { 361 | name: 'smartlead_get_sender_accounts', 362 | description: 'Get the list of all sender accounts selected for a specific spam test.', 363 | category: ToolCategory.SMART_DELIVERY, 364 | inputSchema: { 365 | type: 'object', 366 | properties: { 367 | spam_test_id: { 368 | type: 'integer', 369 | description: 'ID of the spam test to get the sender accounts for', 370 | }, 371 | }, 372 | required: ['spam_test_id'], 373 | }, 374 | }; 375 | 376 | export const GET_BLACKLIST_TOOL: CategoryTool = { 377 | name: 'smartlead_get_blacklist', 378 | description: 'Get the list of all blacklists per IP per email sent.', 379 | category: ToolCategory.SMART_DELIVERY, 380 | inputSchema: { 381 | type: 'object', 382 | properties: { 383 | spam_test_id: { 384 | type: 'integer', 385 | description: 'ID of the spam test to get the blacklist information for', 386 | }, 387 | }, 388 | required: ['spam_test_id'], 389 | }, 390 | }; 391 | 392 | export const GET_EMAIL_CONTENT_TOOL: CategoryTool = { 393 | name: 'smartlead_get_email_content', 394 | description: 'Get details for the email content (raw, HTML) along with campaign and sequence details.', 395 | category: ToolCategory.SMART_DELIVERY, 396 | inputSchema: { 397 | type: 'object', 398 | properties: { 399 | spam_test_id: { 400 | type: 'integer', 401 | description: 'ID of the spam test to get the email content for', 402 | }, 403 | }, 404 | required: ['spam_test_id'], 405 | }, 406 | }; 407 | 408 | export const GET_IP_ANALYTICS_TOOL: CategoryTool = { 409 | name: 'smartlead_get_ip_analytics', 410 | description: 'Get total blacklist count identified in the test.', 411 | category: ToolCategory.SMART_DELIVERY, 412 | inputSchema: { 413 | type: 'object', 414 | properties: { 415 | spam_test_id: { 416 | type: 'integer', 417 | description: 'ID of the spam test to get the IP analytics for', 418 | }, 419 | }, 420 | required: ['spam_test_id'], 421 | }, 422 | }; 423 | 424 | export const GET_EMAIL_HEADERS_TOOL: CategoryTool = { 425 | name: 'smartlead_get_email_headers', 426 | description: 'Get details of the email headers for a specific email.', 427 | category: ToolCategory.SMART_DELIVERY, 428 | inputSchema: { 429 | type: 'object', 430 | properties: { 431 | spam_test_id: { 432 | type: 'integer', 433 | description: 'ID of the spam test', 434 | }, 435 | reply_id: { 436 | type: 'integer', 437 | description: 'ID of the email received by the seed account', 438 | }, 439 | }, 440 | required: ['spam_test_id', 'reply_id'], 441 | }, 442 | }; 443 | 444 | export const GET_SCHEDULE_HISTORY_TOOL: CategoryTool = { 445 | name: 'smartlead_get_schedule_history', 446 | description: 'Get the list and summary of all tests that ran for a particular automated test.', 447 | category: ToolCategory.SMART_DELIVERY, 448 | inputSchema: { 449 | type: 'object', 450 | properties: { 451 | spam_test_id: { 452 | type: 'integer', 453 | description: 'ID of the automated spam test to get the schedule history for', 454 | }, 455 | }, 456 | required: ['spam_test_id'], 457 | }, 458 | }; 459 | 460 | export const GET_IP_DETAILS_TOOL: CategoryTool = { 461 | name: 'smartlead_get_ip_details', 462 | description: 'Get the list of all blacklists per IP for a specific email.', 463 | category: ToolCategory.SMART_DELIVERY, 464 | inputSchema: { 465 | type: 'object', 466 | properties: { 467 | spam_test_id: { 468 | type: 'integer', 469 | description: 'ID of the spam test', 470 | }, 471 | reply_id: { 472 | type: 'integer', 473 | description: 'ID of the email received by the seed account', 474 | }, 475 | }, 476 | required: ['spam_test_id', 'reply_id'], 477 | }, 478 | }; 479 | 480 | export const GET_MAILBOX_SUMMARY_TOOL: CategoryTool = { 481 | name: 'smartlead_get_mailbox_summary', 482 | description: 'Get the list of mailboxes used for any Smart Delivery test with overall performance across all tests.', 483 | category: ToolCategory.SMART_DELIVERY, 484 | inputSchema: { 485 | type: 'object', 486 | properties: { 487 | limit: { 488 | type: 'integer', 489 | description: 'Number of tests to retrieve (default: 10)', 490 | }, 491 | offset: { 492 | type: 'integer', 493 | description: 'Offset for pagination (default: 0)', 494 | }, 495 | }, 496 | }, 497 | }; 498 | 499 | export const GET_MAILBOX_COUNT_TOOL: CategoryTool = { 500 | name: 'smartlead_get_mailbox_count', 501 | description: 'Get the count of all mailboxes used for any spam test.', 502 | category: ToolCategory.SMART_DELIVERY, 503 | inputSchema: { 504 | type: 'object', 505 | properties: {}, 506 | }, 507 | }; 508 | 509 | export const GET_ALL_FOLDERS_TOOL: CategoryTool = { 510 | name: 'smartlead_get_all_folders', 511 | description: 'Get the list and details of all folders created in Smart Delivery along with tests inside each folder.', 512 | category: ToolCategory.SMART_DELIVERY, 513 | inputSchema: { 514 | type: 'object', 515 | properties: { 516 | limit: { 517 | type: 'integer', 518 | description: 'Number of folders to retrieve (default: 10)', 519 | }, 520 | offset: { 521 | type: 'integer', 522 | description: 'Offset for pagination (default: 0)', 523 | }, 524 | name: { 525 | type: 'string', 526 | description: 'Filter folders by name', 527 | }, 528 | }, 529 | }, 530 | }; 531 | 532 | export const CREATE_FOLDER_TOOL: CategoryTool = { 533 | name: 'smartlead_create_folder', 534 | description: 'Create a folder in Smart Delivery to organize tests.', 535 | category: ToolCategory.SMART_DELIVERY, 536 | inputSchema: { 537 | type: 'object', 538 | properties: { 539 | name: { 540 | type: 'string', 541 | description: 'Name of the folder to create', 542 | }, 543 | }, 544 | required: ['name'], 545 | }, 546 | }; 547 | 548 | export const GET_FOLDER_BY_ID_TOOL: CategoryTool = { 549 | name: 'smartlead_get_folder_by_id', 550 | description: 'Get details of a specific folder by ID.', 551 | category: ToolCategory.SMART_DELIVERY, 552 | inputSchema: { 553 | type: 'object', 554 | properties: { 555 | folder_id: { 556 | type: 'integer', 557 | description: 'ID of the folder to retrieve', 558 | }, 559 | }, 560 | required: ['folder_id'], 561 | }, 562 | }; 563 | 564 | export const DELETE_FOLDER_TOOL: CategoryTool = { 565 | name: 'smartlead_delete_folder', 566 | description: 'Delete a folder from Smart Delivery.', 567 | category: ToolCategory.SMART_DELIVERY, 568 | inputSchema: { 569 | type: 'object', 570 | properties: { 571 | folder_id: { 572 | type: 'integer', 573 | description: 'ID of the folder to delete', 574 | }, 575 | }, 576 | required: ['folder_id'], 577 | }, 578 | }; 579 | 580 | // Export all tools as an array for easy registration 581 | export const smartDeliveryTools = [ 582 | GET_REGION_WISE_PROVIDERS_TOOL, 583 | CREATE_MANUAL_PLACEMENT_TEST_TOOL, 584 | CREATE_AUTOMATED_PLACEMENT_TEST_TOOL, 585 | GET_SPAM_TEST_DETAILS_TOOL, 586 | DELETE_SMART_DELIVERY_TESTS_TOOL, 587 | STOP_AUTOMATED_TEST_TOOL, 588 | LIST_ALL_TESTS_TOOL, 589 | GET_PROVIDER_WISE_REPORT_TOOL, 590 | GET_GROUP_WISE_REPORT_TOOL, 591 | GET_SENDER_ACCOUNT_WISE_REPORT_TOOL, 592 | GET_SPAM_FILTER_DETAILS_TOOL, 593 | GET_DKIM_DETAILS_TOOL, 594 | GET_SPF_DETAILS_TOOL, 595 | GET_RDNS_DETAILS_TOOL, 596 | GET_SENDER_ACCOUNTS_TOOL, 597 | GET_BLACKLIST_TOOL, 598 | GET_EMAIL_CONTENT_TOOL, 599 | GET_IP_ANALYTICS_TOOL, 600 | GET_EMAIL_HEADERS_TOOL, 601 | GET_SCHEDULE_HISTORY_TOOL, 602 | GET_IP_DETAILS_TOOL, 603 | GET_MAILBOX_SUMMARY_TOOL, 604 | GET_MAILBOX_COUNT_TOOL, 605 | GET_ALL_FOLDERS_TOOL, 606 | CREATE_FOLDER_TOOL, 607 | GET_FOLDER_BY_ID_TOOL, 608 | DELETE_FOLDER_TOOL, 609 | ]; 610 | ```