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 |
```