This is page 1 of 2. Use http://codebase.md/jean-technologies/smartlead-mcp-server-local?lines=false&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 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Dependency directories node_modules/ # Build output dist/ # Environment variables .env .env.local .env.development.local .env.test.local .env.production.local # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Coverage directory used by tools like istanbul coverage/ # IDE files .idea/ .vscode/ *.swp *.swo # OS files .DS_Store Thumbs.db ``` -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` # Smartlead API Key (required) SMARTLEAD_API_KEY=your_api_key_here # Optional: Custom API URL (defaults to https://server.smartlead.ai/api/v1) # SMARTLEAD_API_URL=https://custom-server.smartlead.ai/api/v1 # Retry configuration SMARTLEAD_RETRY_MAX_ATTEMPTS=3 SMARTLEAD_RETRY_INITIAL_DELAY=1000 SMARTLEAD_RETRY_MAX_DELAY=10000 SMARTLEAD_RETRY_BACKOFF_FACTOR=2 # Supergateway Integration # Set to 'true' to enable Supergateway integration USE_SUPERGATEWAY=false # Required if USE_SUPERGATEWAY is true # Download Tracking Configuration (optional) # DOWNLOAD_LOG_PATH=/custom/path/to/downloads.json # Enable Features # All features are enabled by default. No license key required. ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown [](https://mseep.ai/app/jean-technologies-smartlead-mcp-server-local) # Smartlead Simplified MCP Server [](https://smithery.ai/server/@jean-technologies/smartlead-mcp-server-local) This application provides a simplified interface to the Smartlead API, allowing AI assistants and automation tools to interact with Smartlead's email marketing features. We welcome contribution from the community. **Licensing:** All features are now enabled by default with maximum permissiveness! No license key required. > **For developer details:** See [DEVELOPER_ONBOARDING.md](./DEVELOPER_ONBOARDING.md) ## Quick Start ### Installation ```bash npm install [email protected] ``` or use directly with npx (no installation needed): ### Installing via Smithery To install Smartlead Campaign Management Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@jean-technologies/smartlead-mcp-server-local): ```bash npx -y @smithery/cli install @jean-technologies/smartlead-mcp-server-local --client claude ``` ### With Claude: ```bash npx smartlead-mcp-server start ``` ### With n8n: ```bash npx smartlead-mcp-server sse ``` First run will prompt for your Smartlead API Key. No license key is required. ## Integration Examples ### Claude Extension: ```json { "mcpServers": { "smartlead": { "command": "npx", "args": ["smartlead-mcp-server", "start"], "env": { "SMARTLEAD_API_KEY": "your_api_key_here" } } } } ``` ### n8n Setup: 1. Start the server: `npx smartlead-mcp-server sse` 2. Configure n8n MCP Client node with: - SSE URL: `http://localhost:3000/sse` - Message URL: `http://localhost:3000/message` ## Available Features **All features are now enabled by default, including:** - Campaign & Lead Management - Statistics and Analytics - Smart Delivery & Webhooks - n8n Integration - Client Management - Smart Senders - Download Tracking and Analytics ## New Download Tracking Features This release adds new download tracking capabilities: ### Download Campaign Data Download campaign data with tracking using the `smartlead_download_campaign_data` tool: ```json { "campaign_id": 12345, "download_type": "analytics", // "analytics", "leads", "sequence", "full_export" "format": "json", // "json" or "csv" "user_id": "optional-user-identifier" } ``` ### View Download Statistics View download statistics using the `smartlead_view_download_statistics` tool: ```json { "time_period": "all", // "all", "today", "week", "month" "group_by": "type" // "type", "format", "campaign", "date" } ``` All downloads are tracked in `~/.smartlead-mcp/downloads.json` for analytics. ## Need Help? - Run `npx smartlead-mcp-server config` to set up credentials - Use `--api-key` option for non-interactive setup - Contact: [email protected] - Website: [jeantechnologies.com](https://jeantechnologies.com) ## License This software is proprietary and confidential. Unauthorized copying, redistribution, or use of this software, in whole or in part, via any medium, is strictly prohibited without the express permission of Jean Technologies. Copyright © 2025 Jean Technologies. All rights reserved. ``` -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- ```javascript export default { transform: {}, extensionsToTreatAsEsm: ['.ts'], moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, testEnvironment: 'node', verbose: true, testTimeout: 10000 }; ``` -------------------------------------------------------------------------------- /mcp_settings_example.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "smartlead": { "command": "node", "args": ["E:/mcp-servers/smartlead/dist/index.js"], "env": { "SMARTLEAD_API_KEY": "your_api_key_here" }, "disabled": false, "autoApprove": [], "name": "@jean-technologies/smartlead-mcp" } } } ``` -------------------------------------------------------------------------------- /src/types/statistics.d.ts: -------------------------------------------------------------------------------- ```typescript export interface CampaignMailboxStatisticsParams { campaign_id: number; } export interface DownloadCampaignDataParams { campaign_id: number; download_type: 'analytics' | 'leads' | 'sequence' | 'full_export'; format: 'json' | 'csv'; user_id?: string; } export interface ViewDownloadStatisticsParams { time_period?: 'all' | 'today' | 'week' | 'month'; group_by?: 'type' | 'format' | 'campaign' | 'date'; } ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "esModuleInterop": true, "strict": true, "outDir": "dist", "declaration": true, "sourceMap": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "typeRoots": ["./node_modules/@types", "./src/types"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.test.ts"] } ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile # Generated by https://smithery.ai. See: https://smithery.ai/docs/build/project-config FROM node:lts-alpine WORKDIR /app # Install build tools and dependencies COPY package.json package-lock.json tsconfig.json ./ COPY src ./src RUN apk add --no-cache python3 make g++ \ && npm ci --ignore-scripts \ && npm run build \ && npm prune --production \ && apk del python3 make g++ # Default command for stdio usage CMD ["node", "dist/index.js"] ``` -------------------------------------------------------------------------------- /src/tools/lead.d.ts: -------------------------------------------------------------------------------- ```typescript // Type declarations for Lead tools declare module './tools/lead.js' { import { CategoryTool } from '../types/common.js'; export const LIST_LEADS_TOOL: CategoryTool; export const GET_LEAD_TOOL: CategoryTool; export const ADD_LEAD_TO_CAMPAIGN_TOOL: CategoryTool; export const UPDATE_LEAD_TOOL: CategoryTool; export const UPDATE_LEAD_STATUS_TOOL: CategoryTool; export const BULK_IMPORT_LEADS_TOOL: CategoryTool; export const DELETE_LEAD_TOOL: CategoryTool; export const leadTools: CategoryTool[]; } ``` -------------------------------------------------------------------------------- /src/types/common.ts: -------------------------------------------------------------------------------- ```typescript import { Tool } from '@modelcontextprotocol/sdk/types.js'; // Extended Tool type with category information export interface CategoryTool extends Tool { category: string; } // Categories enum export enum ToolCategory { CAMPAIGN_MANAGEMENT = 'campaignManagement', EMAIL_ACCOUNT_MANAGEMENT = 'emailAccountManagement', LEAD_MANAGEMENT = 'leadManagement', CAMPAIGN_STATISTICS = 'campaignStatistics', SMART_DELIVERY = 'smartDelivery', WEBHOOKS = 'webhooks', CLIENT_MANAGEMENT = 'clientManagement', SMART_SENDERS = 'smartSenders' } ``` -------------------------------------------------------------------------------- /src/tools/statistics.d.ts: -------------------------------------------------------------------------------- ```typescript // Type declarations for Statistics tools declare module './tools/statistics.js' { import { CategoryTool } from '../types/common.js'; export const CAMPAIGN_STATISTICS_TOOL: CategoryTool; export const CAMPAIGN_STATISTICS_BY_DATE_TOOL: CategoryTool; export const WARMUP_STATS_BY_EMAIL_TOOL: CategoryTool; export const CAMPAIGN_TOP_LEVEL_ANALYTICS_TOOL: CategoryTool; export const CAMPAIGN_TOP_LEVEL_ANALYTICS_BY_DATE_TOOL: CategoryTool; export const CAMPAIGN_LEAD_STATISTICS_TOOL: CategoryTool; export const CAMPAIGN_MAILBOX_STATISTICS_TOOL: CategoryTool; export const statisticsTools: CategoryTool[]; } ``` -------------------------------------------------------------------------------- /src/tools/email.d.ts: -------------------------------------------------------------------------------- ```typescript // Type declarations for Email tools declare module './tools/email.js' { import { CategoryTool } from '../types/common.js'; export const LIST_EMAIL_ACCOUNTS_CAMPAIGN_TOOL: CategoryTool; export const ADD_EMAIL_TO_CAMPAIGN_TOOL: CategoryTool; export const REMOVE_EMAIL_FROM_CAMPAIGN_TOOL: CategoryTool; export const FETCH_EMAIL_ACCOUNTS_TOOL: CategoryTool; export const CREATE_EMAIL_ACCOUNT_TOOL: CategoryTool; export const UPDATE_EMAIL_ACCOUNT_TOOL: CategoryTool; export const FETCH_EMAIL_ACCOUNT_BY_ID_TOOL: CategoryTool; export const UPDATE_EMAIL_WARMUP_TOOL: CategoryTool; export const RECONNECT_EMAIL_ACCOUNT_TOOL: CategoryTool; export const UPDATE_EMAIL_ACCOUNT_TAG_TOOL: CategoryTool; export const emailTools: CategoryTool[]; } ``` -------------------------------------------------------------------------------- /src/types/supergateway.d.ts: -------------------------------------------------------------------------------- ```typescript /** * Type definitions for Supergateway */ declare module 'supergateway' { export class SuperGateway { /** * Create a new SuperGateway instance * @param options Configuration options including API key */ constructor(options?: { apiKey?: string, [key: string]: any }); /** * Process a request * @param input Input text to process * @param options Processing options * @returns Processed text */ process(input: string, options?: Record<string, any>): Promise<string>; /** * Stream a request response * @param input Input text to process * @param options Processing options * @returns Readable stream with response chunks */ stream(input: string, options?: Record<string, any>): Promise<ReadableStream>; /** * Close the client connection */ close(): Promise<void>; } } ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml # Smithery configuration file: https://smithery.ai/docs/build/project-config startCommand: type: stdio commandFunction: # A JS function that produces the CLI command based on the given config to start the MCP on stdio. |- (config) => ({ command: 'node', args: ['dist/index.js'], env: { SMARTLEAD_API_KEY: config.smartleadApiKey, JEAN_LICENSE_KEY: config.jeanLicenseKey || 'JEANPARTNER', ...(config.smartleadApiUrl ? { SMARTLEAD_API_URL: config.smartleadApiUrl } : {}) } }) configSchema: # JSON Schema defining the configuration options for the MCP. type: object required: - smartleadApiKey properties: smartleadApiKey: type: string description: Smartlead API Key jeanLicenseKey: type: string default: JEANPARTNER description: Jean License Key smartleadApiUrl: type: string default: https://server.smartlead.ai/api/v1 description: Optional Smartlead API URL exampleConfig: smartleadApiKey: YOUR_SMARTLEAD_API_KEY jeanLicenseKey: JEANPARTNER smartleadApiUrl: https://server.smartlead.ai/api/v1 ``` -------------------------------------------------------------------------------- /src/types/clientManagement.ts: -------------------------------------------------------------------------------- ```typescript // Type definitions for Client Management functionality // Client permission types export type ClientPermission = 'reply_master_inbox' | 'full_access' | string; // Add Client To System export interface AddClientParams { name: string; email: string; permission: ClientPermission[]; logo?: string; logo_url?: string | null; password: string; } // Fetch all clients export interface FetchAllClientsParams { // This endpoint doesn't require specific parameters beyond the API key // which is handled at the API client level } // Type guards export function isAddClientParams(args: unknown): args is AddClientParams { if (typeof args !== 'object' || args === null) return false; const params = args as Partial<AddClientParams>; return ( typeof params.name === 'string' && typeof params.email === 'string' && Array.isArray(params.permission) && params.permission.every(perm => typeof perm === 'string') && typeof params.password === 'string' && (params.logo === undefined || typeof params.logo === 'string') && (params.logo_url === undefined || params.logo_url === null || typeof params.logo_url === 'string') ); } export function isFetchAllClientsParams(args: unknown): args is FetchAllClientsParams { // Since this endpoint doesn't require specific parameters beyond the API key // Any object is valid return typeof args === 'object' && args !== null; } ``` -------------------------------------------------------------------------------- /src/tools/clientManagement.ts: -------------------------------------------------------------------------------- ```typescript import { CategoryTool, ToolCategory } from '../types/common.js'; // Client Management Tools export const ADD_CLIENT_TOOL: CategoryTool = { name: 'smartlead_add_client', description: 'Add a new client to the system, optionally with white-label settings.', category: ToolCategory.CLIENT_MANAGEMENT, inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the client', }, email: { type: 'string', description: 'Email address of the client', }, permission: { type: 'array', items: { type: 'string' }, description: 'Array of permissions to grant to the client. Use ["full_access"] for full permissions.', }, logo: { type: 'string', description: 'Logo text or identifier', }, logo_url: { type: ['string', 'null'], description: 'URL to the client\'s logo image', }, password: { type: 'string', description: 'Password for the client\'s account', }, }, required: ['name', 'email', 'permission', 'password'], }, }; export const FETCH_ALL_CLIENTS_TOOL: CategoryTool = { name: 'smartlead_fetch_all_clients', description: 'Retrieve a list of all clients in the system.', category: ToolCategory.CLIENT_MANAGEMENT, inputSchema: { type: 'object', properties: { // This endpoint doesn't require specific parameters beyond the API key // which is handled at the API client level }, required: [], }, }; // Export all tools as an array for easy registration export const clientManagementTools = [ ADD_CLIENT_TOOL, FETCH_ALL_CLIENTS_TOOL, ]; ``` -------------------------------------------------------------------------------- /src/config/feature-config.ts: -------------------------------------------------------------------------------- ```typescript import { isCategoryEnabled } from '../licensing/index.js'; // Configuration for enabling/disabling feature categories // These are the default values when license validation is unavailable export const enabledCategories = { campaignManagement: true, emailAccountManagement: true, leadManagement: true, campaignStatistics: true, smartDelivery: true, webhooks: true, clientManagement: true, smartSenders: true }; // Configuration for enabling/disabling individual tools // This overrides category settings for specific tools export const enabledTools: Record<string, boolean> = { // Override specific tools if needed // Example: To enable a specific tool in a disabled category: // smartlead_fetch_campaign_sequence: true, }; // Feature flags for experimental features export const featureFlags = { betaFeatures: process.env.ENABLE_BETA_FEATURES === 'true', extendedLogging: process.env.EXTENDED_LOGGING === 'true', n8nIntegration: false // Will be set by license validation }; // Helper function to check if a tool should be enabled export async function isToolEnabled(toolName: string, category: string): Promise<boolean> { // Always enable all tools return true; // The following code is kept for reference but will never execute // // Check if the tool has a specific override // if (enabledTools[toolName] !== undefined) { // return enabledTools[toolName]; // } // // // Otherwise, check if the category is enabled by the license // try { // return await isCategoryEnabled(category); // } catch (error) { // // Fallback to default configuration if license check fails // console.error(`License validation failed, using default configuration: ${error}`); // const categoryKey = category as keyof typeof enabledCategories; // return enabledCategories[categoryKey] || false; // } } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "smartlead-mcp-server", "version": "1.2.1", "description": "MCP server for Smartlead campaign management integration. Features include creating campaigns, updating campaign settings, and managing campaign sequences.", "author": "Jonathan Politzki", "type": "module", "bin": { "smartlead-mcp": "dist/cli.js" }, "files": [ "dist" ], "scripts": { "build": "tsc", "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "test:supergateway": "node test_supergateway.js", "start": "node dist/index.js", "start:supergateway": "USE_SUPERGATEWAY=true SUPERGATEWAY_API_KEY=test_key node dist/index.js", "start:sse": "npx -y supergateway --stdio \"node dist/index.js\" --port 3000", "start:sse-supergateway": "npx -y supergateway --stdio \"USE_SUPERGATEWAY=true SUPERGATEWAY_API_KEY=test_key node dist/index.js\" --port 3000", "dev": "npm run build && npm start", "dev:supergateway": "npm run build && npm run start:supergateway", "dev:sse": "npm run build && npm run start:sse", "dev:sse-supergateway": "npm run build && npm run start:sse-supergateway", "lint": "eslint src/**/*.ts", "lint:fix": "eslint src/**/*.ts --fix", "format": "prettier --write .", "prepare": "npm run build" }, "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.4.1", "axios": "^1.6.2", "commander": "^13.1.0", "dotenv": "^16.4.7", "mcp-proxy-auth": "^1.0.2", "p-queue": "^8.0.1", "uuid": "^11.1.0" }, "devDependencies": { "@jest/globals": "^29.7.0", "@types/jest": "^29.5.14", "@types/node": "^20.10.5", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "jest": "^29.7.0", "jest-mock-extended": "^4.0.0-beta1", "prettier": "^3.1.1", "ts-jest": "^29.1.1", "typescript": "^5.3.3" }, "engines": { "node": ">=18.0.0" }, "keywords": [ "mcp", "smartlead", "campaign-management", "email-marketing" ] } ``` -------------------------------------------------------------------------------- /src/supergateway-mock.ts: -------------------------------------------------------------------------------- ```typescript /** * Mock implementation of the SuperGateway client * This is used as a fallback when the real SuperGateway package is not available */ /** * SuperGateway client mock implementation */ export class SuperGateway { private apiKey: string | undefined; private options: Record<string, any>; /** * Create a new SuperGateway instance * @param options Configuration options */ constructor(options: { apiKey?: string, [key: string]: any } = {}) { console.error('[Supergateway] Initialized with API key:', options.apiKey ? '[REDACTED]' : 'undefined'); this.apiKey = options.apiKey; this.options = { ...options }; // Remove apiKey from options to avoid logging it delete this.options.apiKey; } /** * Process a request * @param input Input text to process * @param options Processing options * @returns Processed text */ async process(input: string, options?: Record<string, any>): Promise<string> { console.error('[Supergateway] Processing request'); console.error('[Supergateway] Input:', input); console.error('[Supergateway] Options:', options); // Simulate processing delay await new Promise(resolve => setTimeout(resolve, 500)); // Return a processed response return `[Supergateway Mock] Processed: ${input.substring(0, 30)}...`; } /** * Stream a request response * @param input Input text to process * @param options Processing options * @returns Readable stream with response chunks */ async stream(input: string, options?: Record<string, any>): Promise<ReadableStream> { console.error('[Supergateway] Streaming request'); console.error('[Supergateway] Input:', input); console.error('[Supergateway] Options:', options); // Create a readable stream for the response return new ReadableStream({ start(controller) { // Simulate streaming with delays const encoder = new TextEncoder(); // Send chunks with delays setTimeout(() => { controller.enqueue(encoder.encode('[Supergateway] Stream started\n\n')); }, 100); setTimeout(() => { controller.enqueue(encoder.encode('Processing input...\n\n')); }, 500); setTimeout(() => { controller.enqueue(encoder.encode('Generating response...\n\n')); }, 1000); setTimeout(() => { controller.enqueue(encoder.encode('Completed processing\n\n')); controller.close(); }, 1500); } }); } /** * Close the client connection */ async close(): Promise<void> { console.error('[Supergateway] Connection closed'); } } ``` -------------------------------------------------------------------------------- /llms-install.md: -------------------------------------------------------------------------------- ```markdown # Smartlead MCP Server - Installation Guide for AI Assistants This guide helps AI assistants like Cline set up the Smartlead MCP Server. This server allows you to interact with Smartlead's email marketing API through the Model Context Protocol (MCP). ## Prerequisites - Node.js (v18 or higher) - npm (comes with Node.js) - A Smartlead API Key (user will need to provide this) - Optional: A License Key (free tier is available, paid tiers have more features) ## One-Click Installation Method The easiest way to install this MCP server is using the npx command: ```bash # Create a directory for configurations if desired (recommended) mkdir -p ~/.config/smartlead-mcp cd ~/.config/smartlead-mcp # Run the configuration helper npx smartlead-mcp-server config ``` This will guide you through an interactive setup process to configure the Smartlead API key and license key. The user will need to provide their Smartlead API key when prompted. ## Start the Server After installation and configuration, you can start the server in different modes depending on the intended use: ### For Claude and other AI assistants (STDIO mode): ```bash npx smartlead-mcp-server start ``` ### For n8n automation (SSE mode): ```bash npx smartlead-mcp-server sse ``` ## Integration with Cline To set up Cline to use this MCP server: 1. You'll need to add it to Cline's MCP settings: ```json { "mcpServers": { "smartlead": { "command": "npx", "args": ["smartlead-mcp-server", "start"], "env": { "SMARTLEAD_API_KEY": "user_provides_api_key_here", "JEAN_LICENSE_KEY": "optional_license_key_here" } } } } ``` 2. Ask the user for their Smartlead API key and update the configuration accordingly. 3. For the license key, you can use "JEANPARTNER" for the free tier or ask the user if they have a paid license key. ## Features and Capabilities This MCP server provides tools for: - **FREE Tier (20+ tools):** - Campaign Management - Lead Management - **BASIC Tier (50+ tools):** - All FREE tier features - Campaign Statistics - Smart Delivery (Spam Tests, DNS Checks) - Webhooks - n8n Integration - **PREMIUM Tier (All tools):** - All BASIC tier features - Client Management - Smart Senders (Domain/Mailbox Purchase) - Advanced Features ## Troubleshooting - If you encounter issues with configuration, run: `npx smartlead-mcp-server config` - For license-related issues, use "JEANPARTNER" as the license key for free tier access - If you need more tools, direct the user to [https://sea-turtle-app-64etr.ondigitalocean.app/](https://sea-turtle-app-64etr.ondigitalocean.app/) to purchase a license ## License Information This MCP server is available under the MIT license. The number of machines allowed varies by license tier: - FREE tier: 1 machine - BASIC tier: 2 machines - PREMIUM tier: 5 machines ``` -------------------------------------------------------------------------------- /src/supergateway.ts: -------------------------------------------------------------------------------- ```typescript import { SuperGateway as MockSuperGateway } from './supergateway-mock.js'; import { isFeatureEnabled, LicenseLevel } from './licensing/index.js'; /** * Interface for SupergateWay options */ export interface SuperGatewayOptions { apiKey?: string; [key: string]: any; } /** * Interface for SuperGateway API */ export interface SuperGateway { process(input: string, options?: Record<string, any>): Promise<string>; stream(input: string, options?: Record<string, any>): Promise<ReadableStream>; close(): Promise<void>; } /** * Try to dynamically import Supergateway package */ export async function tryImportSupergateway(): Promise<any> { try { // First try to import the real package return await import('supergateway'); } catch (error) { console.error(`Failed to import Supergateway: ${error instanceof Error ? error.message : String(error)}`); console.error('Falling back to mock Supergateway implementation'); // Return our mock implementation as a fallback return { SuperGateway: MockSuperGateway }; } } /** * Create a Supergateway instance */ export async function createSupergateway(apiKey?: string): Promise<SuperGateway | null> { try { // Check if the current license allows n8n integration const n8nIntegrationEnabled = await isFeatureEnabled('n8nIntegration'); if (!n8nIntegrationEnabled) { console.error('============================================================='); console.error('ERROR: Your license does not include n8n integration features'); console.error('This feature requires a Basic or Premium license subscription.'); console.error('Visit https://yourservice.com/pricing to upgrade your plan.'); console.error(''); console.error('For testing purposes, you can obtain a Basic license from'); console.error('https://yourservice.com/pricing'); console.error('============================================================='); return null; } // Try to dynamically import the Supergateway module const supergateModule = await tryImportSupergateway(); if (!supergateModule) { console.error('Supergateway module not found.'); return null; } // Check if API key is provided if (!apiKey) { console.error('SUPERGATEWAY_API_KEY not provided'); console.error('Please set the SUPERGATEWAY_API_KEY environment variable in your .env file.'); return null; } // Create Supergateway instance const { SuperGateway } = supergateModule; const gateway = new SuperGateway({ apiKey, // Additional configuration options can be added here }); console.error('Supergateway initialized successfully'); return gateway; } catch (error) { console.error(`Error initializing Supergateway: ${error instanceof Error ? error.message : String(error)}`); return null; } } ``` -------------------------------------------------------------------------------- /DEVELOPER_ONBOARDING.md: -------------------------------------------------------------------------------- ```markdown # Smartlead MCP Server - Developer Guide ## Overview Smartlead MCP Server provides an organized interface to the Smartlead API using the Model Context Protocol (MCP), enabling AI assistants and automation tools to manage email marketing campaigns. ## Prerequisites - Node.js (v18+) - A Smartlead API Key ## Quick Start Options ### Option 1: Use npx (Recommended) ```bash # For Claude npx smartlead-mcp-server start # For n8n npx smartlead-mcp-server sse ``` ### Option 2: Development Setup ```bash # Clone and install git clone https://github.com/jean-technologies/smartlead-mcp-server-local.git cd smartlead-mcp-server-local npm install # Configure and build cp .env.example .env # Then edit with your API key npm run build # Run npm start # For Claude # OR npm run start:sse # For n8n ``` ## Two Integration Pathways ### 1. Claude Integration Add to Claude settings JSON: ```json { "mcp": { "name": "smartlead", "command": "npx", "args": ["smartlead-mcp-server", "start"], "env": { "SMARTLEAD_API_KEY": "your_api_key_here" } } } ``` ### 2. n8n Integration #### Local n8n: 1. Run server: `npx smartlead-mcp-server sse` 2. Configure n8n MCP node with: - SSE URL: `http://localhost:3000/sse` - Message URL: `http://localhost:3000/message` #### n8n Cloud: 1. Run server: `npx smartlead-mcp-server sse` 2. Create tunnel: `npx ngrok http 3000` 3. Use ngrok URL in n8n MCP node ## Available Features All features are now enabled by default, including: - Campaign & Lead Management - Statistics and Analytics - Smart Delivery & Webhooks - n8n Integration - Client Management - Smart Senders - Download Tracking and Analytics ## Download Tracking System ### Implementation Details The download tracking system stores records in `~/.smartlead-mcp/downloads.json` with the following structure: ```json { "downloads": [ { "id": "unique-download-id", "timestamp": "2023-06-15T12:34:56.789Z", "campaignId": 12345, "downloadType": "analytics", "format": "json", "userId": "optional-user-id", "machineId": "machine-identifier" } ] } ``` ### Available Tools 1. **Download Campaign Data**: `smartlead_download_campaign_data` - Fetches data from Smartlead API - Converts to CSV if requested - Automatically tracks download details 2. **View Download Statistics**: `smartlead_view_download_statistics` - Filter by time period (all, today, week, month) - Group by various dimensions (type, format, campaign, date) - Get usage insights and recent downloads ## Troubleshooting ### Common Solutions - Configure settings: `npx smartlead-mcp-server config` - Set API key directly: `npx smartlead-mcp-server sse --api-key=YOUR_API_KEY` - Debug mode: `DEBUG=smartlead:* npx smartlead-mcp-server start` ## Resources - [Smartlead API Docs](https://docs.smartlead.ai) - [MCP Specification](https://github.com/modelcontextprotocol/spec) - [n8n Integration Guide](https://docs.n8n.io) ``` -------------------------------------------------------------------------------- /src/handlers/clientManagement.ts: -------------------------------------------------------------------------------- ```typescript import { AxiosInstance } from 'axios'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { isAddClientParams, isFetchAllClientsParams } from '../types/clientManagement.js'; // SmartLead API base URL const SMARTLEAD_API_URL = 'https://server.smartlead.ai/api/v1'; // Handler for Client Management-related tools export async function handleClientManagementTool( toolName: string, args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { switch (toolName) { case 'smartlead_add_client': { return handleAddClient(args, apiClient, withRetry); } case 'smartlead_fetch_all_clients': { return handleFetchAllClients(args, apiClient, withRetry); } default: throw new Error(`Unknown Client Management tool: ${toolName}`); } } // Create a modified client for SmartLead API with the correct base URL function createSmartLeadClient(apiClient: AxiosInstance) { return { get: (url: string, config?: any) => apiClient.get(`${SMARTLEAD_API_URL}${url}`, config), post: (url: string, data?: any, config?: any) => apiClient.post(`${SMARTLEAD_API_URL}${url}`, data, config), put: (url: string, data?: any, config?: any) => apiClient.put(`${SMARTLEAD_API_URL}${url}`, data, config), delete: (url: string, config?: any) => apiClient.delete(`${SMARTLEAD_API_URL}${url}`, config) }; } // Individual handlers for each tool async function handleAddClient( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isAddClientParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_add_client' ); } try { const smartLeadClient = createSmartLeadClient(apiClient); const response = await withRetry( async () => smartLeadClient.post('/client/save', args), 'add client' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleFetchAllClients( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isFetchAllClientsParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_fetch_all_clients' ); } try { const smartLeadClient = createSmartLeadClient(apiClient); const response = await withRetry( async () => smartLeadClient.get('/client/'), 'fetch all clients' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } ``` -------------------------------------------------------------------------------- /src/registry/tool-registry.ts: -------------------------------------------------------------------------------- ```typescript import { CategoryTool } from '../types/common.js'; import { isToolEnabled } from '../config/feature-config.js'; import { validateLicense, LicenseLevel } from '../licensing/index.js'; /** * Tool Registry manages the registration and querying of all available tools. * It provides a central place to register, filter, and retrieve tools by various criteria. */ export class ToolRegistry { private tools: Map<string, CategoryTool> = new Map(); private cachedEnabledTools: CategoryTool[] | null = null; private lastCacheTime = 0; private readonly CACHE_TTL = 60000; // 1 minute /** * Register a single tool in the registry */ register(tool: CategoryTool): void { this.tools.set(tool.name, tool); // Invalidate cache when tools change this.cachedEnabledTools = null; } /** * Register multiple tools at once */ registerMany(tools: CategoryTool[]): void { tools.forEach(tool => this.register(tool)); } /** * Get a tool by its name */ getByName(name: string): CategoryTool | undefined { return this.tools.get(name); } /** * Get all tools that belong to a specific category */ getByCategory(category: string): CategoryTool[] { return Array.from(this.tools.values()) .filter(tool => tool.category === category); } /** * Get all registered tools */ getAllTools(): CategoryTool[] { return Array.from(this.tools.values()); } /** * Get all tools that are enabled based on the license and configuration * This method now returns a subset based on the current license */ async getEnabledToolsAsync(): Promise<CategoryTool[]> { const now = Date.now(); // Use cache if available and not expired if (this.cachedEnabledTools && (now - this.lastCacheTime < this.CACHE_TTL)) { return this.cachedEnabledTools; } // Get license to check allowed categories const license = await validateLicense(); // Filter tools based on license-allowed categories const enabledTools = this.getAllTools().filter(tool => license.features.allowedCategories.includes(tool.category) ); // Cache results this.cachedEnabledTools = enabledTools; this.lastCacheTime = now; return enabledTools; } /** * Get enabled tools (synchronous version, falls back to configuration) * This is used for backward compatibility */ getEnabledTools(): CategoryTool[] { // If we have a cache, use it if (this.cachedEnabledTools) { return this.cachedEnabledTools; } // Otherwise fall back to config-based filtering // This uses the local configuration as a fallback return this.getAllTools().filter(tool => { try { return isToolEnabled(tool.name, tool.category); } catch (e) { // If async check fails, use default behavior return false; } }); } /** * Check if a tool with the given name exists in the registry */ hasToolWithName(name: string): boolean { return this.tools.has(name); } /** * Check if a tool belongs to the specified category */ isToolInCategory(name: string, category: string): boolean { const tool = this.getByName(name); return !!tool && tool.category === category; } } // Create a singleton instance export const toolRegistry = new ToolRegistry(); ``` -------------------------------------------------------------------------------- /src/n8n/index.ts: -------------------------------------------------------------------------------- ```typescript import axios from 'axios'; import { LicenseLevel, validateLicense, getFeatureToken } from '../licensing/index.js'; const N8N_CONFIG = { apiUrl: process.env.N8N_API_URL || 'https://n8n.yourservice.com/api/v1', }; /** * Get workflows from n8n * This is a premium feature that requires server-side validation */ export async function getWorkflows() { // First check if n8n integration is available based on license const licenseInfo = await validateLicense(); if (licenseInfo.level !== LicenseLevel.PREMIUM) { throw new Error( `N8n integration requires a Premium license. Your current license level is ${licenseInfo.level}. ` + 'Please upgrade to access this feature.' ); } // Get a feature token for server-side validation const featureToken = await getFeatureToken(); if (!featureToken) { throw new Error('Unable to validate premium feature access. Please try again later.'); } try { // Make the API call with the feature token const response = await axios.get(`${N8N_CONFIG.apiUrl}/workflows`, { headers: { 'Authorization': `Bearer ${process.env.JEAN_LICENSE_KEY}`, 'X-Feature-Token': featureToken.token } }); return response.data; } catch (error) { // Handle API errors if (axios.isAxiosError(error) && error.response) { // If server rejected the feature token if (error.response.status === 403) { throw new Error('Premium feature access denied. Please check your license status.'); } // Other API errors throw new Error(`Failed to fetch n8n workflows: ${error.response.data?.message || error.message}`); } // Generic error throw new Error(`Error accessing n8n integration: ${error instanceof Error ? error.message : String(error)}`); } } /** * Execute a workflow in n8n * This is a premium feature that requires server-side validation */ export async function executeWorkflow(workflowId: string, data: any) { // First check if n8n integration is available based on license const licenseInfo = await validateLicense(); if (licenseInfo.level !== LicenseLevel.PREMIUM) { throw new Error( `N8n integration requires a Premium license. Your current license level is ${licenseInfo.level}. ` + 'Please upgrade to access this feature.' ); } // Get a feature token for server-side validation const featureToken = await getFeatureToken(); if (!featureToken) { throw new Error('Unable to validate premium feature access. Please try again later.'); } try { // Make the API call with the feature token const response = await axios.post( `${N8N_CONFIG.apiUrl}/workflows/${workflowId}/execute`, data, { headers: { 'Authorization': `Bearer ${process.env.JEAN_LICENSE_KEY}`, 'X-Feature-Token': featureToken.token, 'Content-Type': 'application/json' } } ); return response.data; } catch (error) { // Handle API errors if (axios.isAxiosError(error) && error.response) { // If server rejected the feature token if (error.response.status === 403) { throw new Error('Premium feature access denied. Please check your license status.'); } // Other API errors throw new Error(`Failed to execute n8n workflow: ${error.response.data?.message || error.message}`); } // Generic error throw new Error(`Error accessing n8n integration: ${error instanceof Error ? error.message : String(error)}`); } } ``` -------------------------------------------------------------------------------- /src/types/webhooks.ts: -------------------------------------------------------------------------------- ```typescript // Type definitions for Webhooks functionality // Webhook event types export enum WebhookEventType { EMAIL_SENT = 'EMAIL_SENT', EMAIL_OPEN = 'EMAIL_OPEN', EMAIL_LINK_CLICK = 'EMAIL_LINK_CLICK', EMAIL_REPLY = 'EMAIL_REPLY', LEAD_UNSUBSCRIBED = 'LEAD_UNSUBSCRIBED', LEAD_CATEGORY_UPDATED = 'LEAD_CATEGORY_UPDATED' } // Fetch Webhooks By Campaign ID export interface FetchWebhooksByCampaignParams { campaign_id: string; } // Add / Update Campaign Webhook export interface UpsertCampaignWebhookParams { campaign_id: string; id?: number | null; // Set to null to create a new webhook name: string; webhook_url: string; event_types: WebhookEventType[]; categories?: string[]; } // Delete Campaign Webhook export interface DeleteCampaignWebhookParams { campaign_id: string; id: number; // Webhook ID to delete } // Get Webhooks Publish Summary [Private Beta] export interface GetWebhooksPublishSummaryParams { campaign_id: string; fromTime?: string; // ISO 8601 date-time string toTime?: string; // ISO 8601 date-time string } // Retrigger Failed Events [Private Beta] export interface RetriggerFailedEventsParams { campaign_id: string; fromTime: string; // ISO 8601 date-time string toTime: string; // ISO 8601 date-time string } // Type guards export function isFetchWebhooksByCampaignParams(args: unknown): args is FetchWebhooksByCampaignParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as FetchWebhooksByCampaignParams).campaign_id === 'string' ); } export function isUpsertCampaignWebhookParams(args: unknown): args is UpsertCampaignWebhookParams { if (typeof args !== 'object' || args === null) return false; const params = args as Partial<UpsertCampaignWebhookParams>; return ( typeof params.campaign_id === 'string' && typeof params.name === 'string' && typeof params.webhook_url === 'string' && Array.isArray(params.event_types) && params.event_types.every(type => Object.values(WebhookEventType).includes(type as WebhookEventType) ) && (params.categories === undefined || (Array.isArray(params.categories) && params.categories.every(category => typeof category === 'string') ) ) && (params.id === undefined || params.id === null || typeof params.id === 'number') ); } export function isDeleteCampaignWebhookParams(args: unknown): args is DeleteCampaignWebhookParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as DeleteCampaignWebhookParams).campaign_id === 'string' && 'id' in args && typeof (args as DeleteCampaignWebhookParams).id === 'number' ); } export function isGetWebhooksPublishSummaryParams(args: unknown): args is GetWebhooksPublishSummaryParams { if (typeof args !== 'object' || args === null) return false; const params = args as Partial<GetWebhooksPublishSummaryParams>; return ( typeof params.campaign_id === 'string' && (params.fromTime === undefined || typeof params.fromTime === 'string') && (params.toTime === undefined || typeof params.toTime === 'string') ); } export function isRetriggerFailedEventsParams(args: unknown): args is RetriggerFailedEventsParams { if (typeof args !== 'object' || args === null) return false; const params = args as Partial<RetriggerFailedEventsParams>; return ( typeof params.campaign_id === 'string' && typeof params.fromTime === 'string' && typeof params.toTime === 'string' ); } ``` -------------------------------------------------------------------------------- /src/types/email.d.ts: -------------------------------------------------------------------------------- ```typescript // Type declarations for Email Account parameters declare module '../types/email.js' { export function isListEmailAccountsParams(args: unknown): args is ListEmailAccountsParams; export function isAddEmailToCampaignParams(args: unknown): args is AddEmailToCampaignParams; export function isRemoveEmailFromCampaignParams(args: unknown): args is RemoveEmailFromCampaignParams; export function isFetchEmailAccountsParams(args: unknown): args is FetchEmailAccountsParams; export function isCreateEmailAccountParams(args: unknown): args is CreateEmailAccountParams; export function isUpdateEmailAccountParams(args: unknown): args is UpdateEmailAccountParams; export function isFetchEmailAccountByIdParams(args: unknown): args is FetchEmailAccountByIdParams; export function isUpdateEmailWarmupParams(args: unknown): args is UpdateEmailWarmupParams; export function isReconnectEmailAccountParams(args: unknown): args is ReconnectEmailAccountParams; export function isUpdateEmailAccountTagParams(args: unknown): args is UpdateEmailAccountTagParams; export interface ListEmailAccountsParams { campaign_id?: number; status?: string; limit?: number; offset?: number; } export interface AddEmailToCampaignParams { campaign_id: number; email_account_id: number; } export interface RemoveEmailFromCampaignParams { campaign_id: number; email_account_id: number; } export interface FetchEmailAccountsParams { status?: string; limit?: number; offset?: number; username?: string; client_id?: number; } export interface CreateEmailAccountParams { from_name: string; from_email: string; user_name: string; password: string; smtp_host: string; smtp_port: number; imap_host: string; imap_port: number; max_email_per_day?: number; custom_tracking_url?: string; bcc?: string; signature?: string; warmup_enabled?: boolean; total_warmup_per_day?: number; daily_rampup?: number; reply_rate_percentage?: number; client_id?: number; } export interface UpdateEmailAccountParams { email_account_id: number; max_email_per_day?: number; custom_tracking_url?: string; bcc?: string; signature?: string; client_id?: number | null; time_to_wait_in_mins?: number; } export interface FetchEmailAccountByIdParams { email_account_id: number; } export interface UpdateEmailWarmupParams { email_account_id: number; warmup_enabled: string; total_warmup_per_day?: number; daily_rampup?: number; reply_rate_percentage?: string; warmup_key_id?: string; } export interface ReconnectEmailAccountParams { email_account_id: number; connection_details?: { smtp_host?: string; smtp_port?: number; smtp_username?: string; smtp_password?: string; imap_host?: string; imap_port?: number; imap_username?: string; imap_password?: string; oauth_token?: string; }; } export interface UpdateEmailAccountTagParams { id: number; name: string; color: string; } export interface EmailAccount { id: number; email: string; name?: string; provider: string; status: string; created_at: string; updated_at: string; last_checked_at?: string; warmup_enabled: boolean; daily_limit?: number; tags?: string[]; } export interface EmailAccountResponse { success: boolean; data: EmailAccount; message?: string; } export interface EmailAccountListResponse { success: boolean; data: { accounts: EmailAccount[]; total: number; }; message?: string; } export interface EmailAccountActionResponse { success: boolean; message: string; } } ``` -------------------------------------------------------------------------------- /src/types/smartSenders.ts: -------------------------------------------------------------------------------- ```typescript // Type definitions for Smart Senders functionality // Get Vendors export interface GetVendorsParams { // This endpoint doesn't require specific parameters beyond the API key // which is handled at the API client level } // Search Domain export interface SearchDomainParams { domain_name: string; vendor_id: number; } // Mailbox details for auto-generate and order placement export interface MailboxDetail { first_name: string; last_name: string; profile_pic?: string; mailbox?: string; // Required only for place-order } // Domain with mailbox details for auto-generate export interface DomainWithMailboxes { domain_name: string; mailbox_details: MailboxDetail[]; } // Auto-generate Mailboxes export interface AutoGenerateMailboxesParams { vendor_id: number; domains: DomainWithMailboxes[]; } // Place order for mailboxes export interface PlaceOrderParams { vendor_id: number; forwarding_domain: string; domains: DomainWithMailboxes[]; } // Get Domain List export interface GetDomainListParams { // This endpoint doesn't require specific parameters beyond the API key // which is handled at the API client level } // Type guards export function isGetVendorsParams(args: unknown): args is GetVendorsParams { // Since this endpoint doesn't require specific parameters beyond the API key // Any object is valid return typeof args === 'object' && args !== null; } export function isSearchDomainParams(args: unknown): args is SearchDomainParams { if (typeof args !== 'object' || args === null) return false; const params = args as Partial<SearchDomainParams>; return ( typeof params.domain_name === 'string' && typeof params.vendor_id === 'number' ); } export function isMailboxDetail(obj: unknown): obj is MailboxDetail { if (typeof obj !== 'object' || obj === null) return false; const detail = obj as Partial<MailboxDetail>; return ( typeof detail.first_name === 'string' && typeof detail.last_name === 'string' && (detail.profile_pic === undefined || typeof detail.profile_pic === 'string') && (detail.mailbox === undefined || typeof detail.mailbox === 'string') ); } export function isDomainWithMailboxes(obj: unknown): obj is DomainWithMailboxes { if (typeof obj !== 'object' || obj === null) return false; const domain = obj as Partial<DomainWithMailboxes>; return ( typeof domain.domain_name === 'string' && Array.isArray(domain.mailbox_details) && domain.mailbox_details.every(detail => isMailboxDetail(detail)) ); } export function isAutoGenerateMailboxesParams(args: unknown): args is AutoGenerateMailboxesParams { if (typeof args !== 'object' || args === null) return false; const params = args as Partial<AutoGenerateMailboxesParams>; return ( typeof params.vendor_id === 'number' && Array.isArray(params.domains) && params.domains.every(domain => isDomainWithMailboxes(domain)) ); } export function isPlaceOrderParams(args: unknown): args is PlaceOrderParams { if (typeof args !== 'object' || args === null) return false; const params = args as Partial<PlaceOrderParams>; // Check if domains have mailbox property in mailbox_details const domainsHaveMailboxes = Array.isArray(params.domains) && params.domains.every(domain => isDomainWithMailboxes(domain) && domain.mailbox_details.every(detail => typeof detail.mailbox === 'string') ); return ( typeof params.vendor_id === 'number' && typeof params.forwarding_domain === 'string' && domainsHaveMailboxes ); } export function isGetDomainListParams(args: unknown): args is GetDomainListParams { // Since this endpoint doesn't require specific parameters beyond the API key // Any object is valid return typeof args === 'object' && args !== null; } ``` -------------------------------------------------------------------------------- /src/tools/webhooks.ts: -------------------------------------------------------------------------------- ```typescript import { CategoryTool, ToolCategory } from '../types/common.js'; import { WebhookEventType } from '../types/webhooks.js'; // Webhook Tools export const FETCH_WEBHOOKS_BY_CAMPAIGN_TOOL: CategoryTool = { name: 'smartlead_fetch_webhooks_by_campaign', description: 'Fetch all the webhooks associated with a campaign using the campaign ID.', category: ToolCategory.WEBHOOKS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'string', description: 'ID of the campaign to fetch webhooks for', }, }, required: ['campaign_id'], }, }; export const UPSERT_CAMPAIGN_WEBHOOK_TOOL: CategoryTool = { name: 'smartlead_upsert_campaign_webhook', description: 'Add or update a webhook for a specific campaign.', category: ToolCategory.WEBHOOKS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'string', description: 'ID of the campaign to add/update webhook for', }, id: { type: ['integer', 'null'], description: 'ID of the webhook to update. Set to null to create a new webhook.', }, name: { type: 'string', description: 'Name for the webhook', }, webhook_url: { type: 'string', description: 'URL to call when the webhook event occurs', }, event_types: { type: 'array', items: { type: 'string', enum: Object.values(WebhookEventType), }, description: `Types of events to trigger the webhook. Options: ${Object.values(WebhookEventType).join(', ')}`, }, categories: { type: 'array', items: { type: 'string' }, description: 'Categories for filtering webhook events (e.g. ["Interested", "NotInterested"])', }, }, required: ['campaign_id', 'name', 'webhook_url', 'event_types'], }, }; export const DELETE_CAMPAIGN_WEBHOOK_TOOL: CategoryTool = { name: 'smartlead_delete_campaign_webhook', description: 'Delete a specific webhook from a campaign.', category: ToolCategory.WEBHOOKS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'string', description: 'ID of the campaign containing the webhook', }, id: { type: 'integer', description: 'ID of the webhook to delete', }, }, required: ['campaign_id', 'id'], }, }; export const GET_WEBHOOKS_PUBLISH_SUMMARY_TOOL: CategoryTool = { name: 'smartlead_get_webhooks_publish_summary', description: 'Get a summary of webhook publish events (Private Beta feature).', category: ToolCategory.WEBHOOKS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'string', description: 'ID of the campaign to get webhook publish summary for', }, fromTime: { type: 'string', description: 'Start date/time in ISO 8601 format (e.g. 2025-03-21T00:00:00Z)', }, toTime: { type: 'string', description: 'End date/time in ISO 8601 format (e.g. 2025-04-04T23:59:59Z)', }, }, required: ['campaign_id'], }, }; export const RETRIGGER_FAILED_EVENTS_TOOL: CategoryTool = { name: 'smartlead_retrigger_failed_events', description: 'Retrigger failed webhook events (Private Beta feature).', category: ToolCategory.WEBHOOKS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'string', description: 'ID of the campaign to retrigger failed webhook events for', }, fromTime: { type: 'string', description: 'Start date/time in ISO 8601 format (e.g. 2025-03-21T00:00:00Z)', }, toTime: { type: 'string', description: 'End date/time in ISO 8601 format (e.g. 2025-04-04T23:59:59Z)', }, }, required: ['campaign_id', 'fromTime', 'toTime'], }, }; // Export all tools as an array for easy registration export const webhookTools = [ FETCH_WEBHOOKS_BY_CAMPAIGN_TOOL, UPSERT_CAMPAIGN_WEBHOOK_TOOL, DELETE_CAMPAIGN_WEBHOOK_TOOL, GET_WEBHOOKS_PUBLISH_SUMMARY_TOOL, RETRIGGER_FAILED_EVENTS_TOOL, ]; ``` -------------------------------------------------------------------------------- /src/utils/download-tracker.ts: -------------------------------------------------------------------------------- ```typescript import fs from 'fs'; import path from 'path'; import os from 'os'; import { v4 as uuidv4 } from 'uuid'; // Interface for download tracking data export interface DownloadRecord { id: string; timestamp: string; campaignId: number; downloadType: string; format: string; userId?: string; machineId: string; ipAddress?: string; } // Path to store download records const DOWNLOAD_LOG_PATH = process.env.DOWNLOAD_LOG_PATH || path.join(os.homedir(), '.smartlead-mcp', 'downloads.json'); // Ensure directory exists const ensureDirectoryExists = (filePath: string) => { const dirname = path.dirname(filePath); if (!fs.existsSync(dirname)) { fs.mkdirSync(dirname, { recursive: true }); } }; // Initialize the download log file if it doesn't exist const initializeLogFile = () => { ensureDirectoryExists(DOWNLOAD_LOG_PATH); if (!fs.existsSync(DOWNLOAD_LOG_PATH)) { fs.writeFileSync(DOWNLOAD_LOG_PATH, JSON.stringify({ downloads: [] }, null, 2)); console.log(`Created download tracking file at: ${DOWNLOAD_LOG_PATH}`); } }; // Get a unique machine identifier const getMachineId = (): string => { try { const os = process.platform; const cpus = process.env.NUMBER_OF_PROCESSORS || ''; const username = process.env.USER || process.env.USERNAME || ''; const hostname = process.env.HOSTNAME || ''; // Create a simple hash of these values const combinedString = `${os}-${cpus}-${username}-${hostname}`; let hash = 0; for (let i = 0; i < combinedString.length; i++) { hash = ((hash << 5) - hash) + combinedString.charCodeAt(i); hash |= 0; // Convert to 32bit integer } return Math.abs(hash).toString(16); } catch (e) { // Fallback to a random ID if we can't get system info return Math.random().toString(36).substring(2, 15); } }; /** * Track a download event * @param campaignId The ID of the campaign being downloaded * @param downloadType The type of download (analytics, leads, etc.) * @param format The format of the download (json, csv) * @param userId Optional user identifier * @param ipAddress Optional IP address of the requester * @returns The unique ID of the download record */ export const trackDownload = ( campaignId: number, downloadType: string, format: string, userId?: string, ipAddress?: string ): string => { try { // Initialize log file if it doesn't exist initializeLogFile(); // Read existing records const data = JSON.parse(fs.readFileSync(DOWNLOAD_LOG_PATH, 'utf8')); // Create new download record const downloadId = uuidv4(); const downloadRecord: DownloadRecord = { id: downloadId, timestamp: new Date().toISOString(), campaignId, downloadType, format, userId, machineId: getMachineId(), ipAddress }; // Add to records and save data.downloads.push(downloadRecord); fs.writeFileSync(DOWNLOAD_LOG_PATH, JSON.stringify(data, null, 2)); console.log(`Tracked download: ${downloadId} for campaign ${campaignId}`); return downloadId; } catch (error) { console.error('Failed to track download:', error); return ''; } }; /** * Get all download records * @returns Array of download records */ export const getDownloadRecords = (): DownloadRecord[] => { try { initializeLogFile(); const data = JSON.parse(fs.readFileSync(DOWNLOAD_LOG_PATH, 'utf8')); return data.downloads || []; } catch (error) { console.error('Failed to get download records:', error); return []; } }; /** * Get download statistics * @returns Statistics about downloads */ export const getDownloadStats = () => { const records = getDownloadRecords(); // Count downloads by type const byType: Record<string, number> = {}; // Count downloads by format const byFormat: Record<string, number> = {}; // Count downloads by campaign const byCampaign: Record<number, number> = {}; // Count unique users const uniqueUsers = new Set<string>(); // Count by date (YYYY-MM-DD) const byDate: Record<string, number> = {}; records.forEach(record => { // Count by type byType[record.downloadType] = (byType[record.downloadType] || 0) + 1; // Count by format byFormat[record.format] = (byFormat[record.format] || 0) + 1; // Count by campaign byCampaign[record.campaignId] = (byCampaign[record.campaignId] || 0) + 1; // Track unique users (either by userId or machineId) if (record.userId) { uniqueUsers.add(record.userId); } else { uniqueUsers.add(record.machineId); } // Count by date const date = record.timestamp.split('T')[0]; byDate[date] = (byDate[date] || 0) + 1; }); return { totalDownloads: records.length, uniqueUsers: uniqueUsers.size, byType, byFormat, byCampaign, byDate }; }; ``` -------------------------------------------------------------------------------- /src/licensing/stripe-integration.js: -------------------------------------------------------------------------------- ```javascript import Stripe from 'stripe'; import axios from 'axios'; import { LicenseLevel } from './index.js'; // Initialize Stripe with your secret key const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || ''); // Your license server URL const LICENSE_SERVER_URL = process.env.LICENSE_SERVER_URL || 'https://api.yourservice.com/licensing'; /** * Check subscription status with Stripe * @param {string} customerId The Stripe customer ID * @returns {Promise<Object>} Subscription information */ export async function getSubscriptionStatus(customerId) { try { // Get all active subscriptions for this customer const subscriptions = await stripe.subscriptions.list({ customer: customerId, status: 'active', expand: ['data.plan.product'] }); if (subscriptions.data.length === 0) { return { active: false, level: LicenseLevel.FREE, message: 'No active subscription found' }; } // Get the highest tier subscription if there are multiple const subscription = subscriptions.data[0]; const product = subscription.items.data[0].plan.product; // Determine license level based on product ID or metadata let level = LicenseLevel.FREE; if (product.metadata.license_level) { level = product.metadata.license_level; } else { // Map product IDs to license levels if metadata not available const productMapping = { 'prod_basic123': LicenseLevel.BASIC, 'prod_premium456': LicenseLevel.PREMIUM }; level = productMapping[product.id] || LicenseLevel.FREE; } return { active: true, level, subscriptionId: subscription.id, currentPeriodEnd: new Date(subscription.current_period_end * 1000), cancelAtPeriodEnd: subscription.cancel_at_period_end, message: 'Subscription active' }; } catch (error) { console.error('Error checking subscription status:', error); return { active: false, level: LicenseLevel.FREE, message: 'Error checking subscription status' }; } } /** * Generate a new license key for a customer * @param {string} customerId The Stripe customer ID * @param {string} level The license level * @returns {Promise<Object>} The generated license information */ export async function generateLicense(customerId, level) { try { // Call your license server to generate a new license const response = await axios.post(`${LICENSE_SERVER_URL}/generate`, { customerId, level }, { headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.LICENSE_API_KEY } }); return response.data; } catch (error) { console.error('Error generating license:', error); throw new Error('Failed to generate license'); } } /** * Handle Stripe webhook events * @param {Object} event The Stripe webhook event * @returns {Promise<Object>} Result of webhook handling */ export async function handleStripeWebhook(event) { try { switch (event.type) { case 'customer.subscription.created': case 'customer.subscription.updated': { const subscription = event.data.object; const customerId = subscription.customer; // Get product information to determine license level const product = await stripe.products.retrieve( subscription.items.data[0].plan.product ); const level = product.metadata.license_level || LicenseLevel.BASIC; // Generate or update license const licenseInfo = await generateLicense(customerId, level); // Update customer metadata with license key await stripe.customers.update(customerId, { metadata: { license_key: licenseInfo.key, license_level: level } }); return { success: true, message: 'License updated', licenseInfo }; } case 'customer.subscription.deleted': { const subscription = event.data.object; const customerId = subscription.customer; // Downgrade to free tier or deactivate license const response = await axios.post(`${LICENSE_SERVER_URL}/downgrade`, { customerId }, { headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.LICENSE_API_KEY } }); // Update customer metadata await stripe.customers.update(customerId, { metadata: { license_level: LicenseLevel.FREE } }); return { success: true, message: 'License downgraded', response: response.data }; } default: return { success: true, message: 'Event ignored' }; } } catch (error) { console.error('Error handling webhook:', error); return { success: false, message: error.message }; } } ``` -------------------------------------------------------------------------------- /src/tools/smartSenders.ts: -------------------------------------------------------------------------------- ```typescript import { CategoryTool, ToolCategory } from '../types/common.js'; // Smart Senders Tools export const GET_VENDORS_TOOL: CategoryTool = { name: 'smartlead_get_vendors', description: 'Retrieve all active domain vendors with their corresponding IDs.', category: ToolCategory.SMART_SENDERS, inputSchema: { type: 'object', properties: { // This endpoint doesn't require specific parameters beyond the API key // which is handled at the API client level }, required: [], }, }; export const SEARCH_DOMAIN_TOOL: CategoryTool = { name: 'smartlead_search_domain', description: 'Search for available domains under $15 that match a given domain name pattern.', category: ToolCategory.SMART_SENDERS, inputSchema: { type: 'object', properties: { domain_name: { type: 'string', description: 'The domain name pattern you want to search for', }, vendor_id: { type: 'integer', description: 'ID of the vendor from whom you want to purchase the domain (use Get Vendors API to retrieve this ID)', }, }, required: ['domain_name', 'vendor_id'], }, }; export const AUTO_GENERATE_MAILBOXES_TOOL: CategoryTool = { name: 'smartlead_auto_generate_mailboxes', description: 'Auto-generate mailboxes based on the domain name and personal details provided.', category: ToolCategory.SMART_SENDERS, inputSchema: { type: 'object', properties: { vendor_id: { type: 'integer', description: 'ID of the vendor from whom you want to purchase the domains and mailboxes', }, domains: { type: 'array', items: { type: 'object', properties: { domain_name: { type: 'string', description: 'The domain name for which you want to generate mailboxes (e.g., example.com)', }, mailbox_details: { type: 'array', items: { type: 'object', properties: { first_name: { type: 'string', description: 'First name for the mailbox owner (should be more than 2 characters and without spaces)', }, last_name: { type: 'string', description: 'Last name for the mailbox owner (should be more than 2 characters and without spaces)', }, profile_pic: { type: 'string', description: 'URL or identifier for profile picture (optional)', }, }, required: ['first_name', 'last_name'], }, description: 'Details for each mailbox you want to generate', }, }, required: ['domain_name', 'mailbox_details'], }, description: 'List of domains and associated mailbox details', }, }, required: ['vendor_id', 'domains'], }, }; export const PLACE_ORDER_MAILBOXES_TOOL: CategoryTool = { name: 'smartlead_place_order_mailboxes', description: 'Confirm and place order for domains and mailboxes to be purchased.', category: ToolCategory.SMART_SENDERS, inputSchema: { type: 'object', properties: { vendor_id: { type: 'integer', description: 'ID of the vendor from whom you want to purchase the domains and mailboxes', }, forwarding_domain: { type: 'string', description: 'The domain to forward to when users access purchased domains', }, domains: { type: 'array', items: { type: 'object', properties: { domain_name: { type: 'string', description: 'The domain name you want to purchase', }, mailbox_details: { type: 'array', items: { type: 'object', properties: { mailbox: { type: 'string', description: 'The complete mailbox address (e.g., [email protected])', }, first_name: { type: 'string', description: 'First name for the mailbox owner', }, last_name: { type: 'string', description: 'Last name for the mailbox owner', }, profile_pic: { type: 'string', description: 'URL or identifier for profile picture (optional)', }, }, required: ['mailbox', 'first_name', 'last_name'], }, description: 'Details for each mailbox you want to purchase', }, }, required: ['domain_name', 'mailbox_details'], }, description: 'List of domains and associated mailbox details for purchase', }, }, required: ['vendor_id', 'forwarding_domain', 'domains'], }, }; export const GET_DOMAIN_LIST_TOOL: CategoryTool = { name: 'smartlead_get_domain_list', description: 'Retrieve a list of all domains purchased through SmartSenders.', category: ToolCategory.SMART_SENDERS, inputSchema: { type: 'object', properties: { // This endpoint doesn't require specific parameters beyond the API key // which is handled at the API client level }, required: [], }, }; // Export all tools as an array for easy registration export const smartSendersTools = [ GET_VENDORS_TOOL, SEARCH_DOMAIN_TOOL, AUTO_GENERATE_MAILBOXES_TOOL, PLACE_ORDER_MAILBOXES_TOOL, GET_DOMAIN_LIST_TOOL, ]; ``` -------------------------------------------------------------------------------- /src/tools/smartDelivery.d.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; // Tool schemas for Smart Delivery category export const regionWiseProviderIdsSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const createManualPlacementTestSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const createAutomatedPlacementTestSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const getSpamTestDetailsSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const deleteSmartDeliveryTestsInBulkSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const stopAutomatedSmartDeliveryTestSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const listAllTestsSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const providerWiseReportSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const geoWiseReportSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const senderAccountWiseReportSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const spamFilterReportSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const dkimDetailsSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const spfDetailsSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const rdnsReportSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const senderAccountListSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const blacklistsSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const spamTestEmailContentSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const spamTestIpBlacklistCountSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const emailReplyHeadersSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const scheduleHistoryForAutomatedTestsSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const ipDetailsSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const mailboxSummarySchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const mailboxCountApiSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const getAllFoldersSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const createFoldersSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const getFolderByIdSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; export const deleteFolderSchema: { name: string; description: string; schema: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>; }; // Type declarations for Smart Delivery tools declare module './tools/smartDelivery.js' { import { CategoryTool } from '../types/common.js'; export const REGION_WISE_PROVIDER_IDS_TOOL: string; export const CREATE_MANUAL_PLACEMENT_TEST_TOOL: string; export const CREATE_AUTOMATED_PLACEMENT_TEST_TOOL: string; export const GET_SPAM_TEST_DETAILS_TOOL: string; export const DELETE_SMART_DELIVERY_TESTS_IN_BULK_TOOL: string; export const STOP_AUTOMATED_SMART_DELIVERY_TEST_TOOL: string; export const LIST_ALL_TESTS_TOOL: string; export const PROVIDER_WISE_REPORT_TOOL: string; export const GEO_WISE_REPORT_TOOL: string; export const SENDER_ACCOUNT_WISE_REPORT_TOOL: string; export const SPAM_FILTER_REPORT_TOOL: string; export const DKIM_DETAILS_TOOL: string; export const SPF_DETAILS_TOOL: string; export const RDNS_REPORT_TOOL: string; export const SENDER_ACCOUNT_LIST_TOOL: string; export const BLACKLISTS_TOOL: string; export const SPAM_TEST_EMAIL_CONTENT_TOOL: string; export const SPAM_TEST_IP_BLACKLIST_COUNT_TOOL: string; export const EMAIL_REPLY_HEADERS_TOOL: string; export const SCHEDULE_HISTORY_FOR_AUTOMATED_TESTS_TOOL: string; export const IP_DETAILS_TOOL: string; export const MAILBOX_SUMMARY_TOOL: string; export const MAILBOX_COUNT_API_TOOL: string; export const GET_ALL_FOLDERS_TOOL: string; export const CREATE_FOLDERS_TOOL: string; export const GET_FOLDER_BY_ID_TOOL: string; export const DELETE_FOLDER_TOOL: string; export const smartDeliveryTools: CategoryTool[]; } ``` -------------------------------------------------------------------------------- /src/tools/lead.ts: -------------------------------------------------------------------------------- ```typescript import { CategoryTool, ToolCategory } from '../types/common.js'; // Lead Management Tools export const LIST_LEADS_TOOL: CategoryTool = { name: 'smartlead_list_leads', description: 'List leads with optional filtering by campaign or status.', category: ToolCategory.LEAD_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'Filter leads by campaign ID', }, status: { type: 'string', description: 'Filter leads by status (e.g., "active", "unsubscribed", "bounced")', }, limit: { type: 'number', description: 'Maximum number of leads to return', }, offset: { type: 'number', description: 'Offset for pagination', }, search: { type: 'string', description: 'Search term to filter leads', }, start_date: { type: 'string', description: 'Filter leads created after this date (YYYY-MM-DD format)', }, end_date: { type: 'string', description: 'Filter leads created before this date (YYYY-MM-DD format)', }, }, }, }; export const GET_LEAD_TOOL: CategoryTool = { name: 'smartlead_get_lead', description: 'Get details of a specific lead by ID.', category: ToolCategory.LEAD_MANAGEMENT, inputSchema: { type: 'object', properties: { lead_id: { type: 'number', description: 'ID of the lead to retrieve', }, }, required: ['lead_id'], }, }; export const ADD_LEAD_TO_CAMPAIGN_TOOL: CategoryTool = { name: 'smartlead_add_lead_to_campaign', description: 'Add a new lead to a campaign.', category: ToolCategory.LEAD_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to add the lead to', }, email: { type: 'string', description: 'Email address of the lead', }, first_name: { type: 'string', description: 'First name of the lead', }, last_name: { type: 'string', description: 'Last name of the lead', }, company: { type: 'string', description: 'Company of the lead', }, title: { type: 'string', description: 'Job title of the lead', }, phone: { type: 'string', description: 'Phone number of the lead', }, custom_fields: { type: 'object', description: 'Custom fields for the lead', }, }, required: ['campaign_id', 'email'], }, }; export const UPDATE_LEAD_TOOL: CategoryTool = { name: 'smartlead_update_lead', description: 'Update an existing lead\'s information.', category: ToolCategory.LEAD_MANAGEMENT, inputSchema: { type: 'object', properties: { lead_id: { type: 'number', description: 'ID of the lead to update', }, email: { type: 'string', description: 'New email address for the lead', }, first_name: { type: 'string', description: 'New first name for the lead', }, last_name: { type: 'string', description: 'New last name for the lead', }, company: { type: 'string', description: 'New company for the lead', }, title: { type: 'string', description: 'New job title for the lead', }, phone: { type: 'string', description: 'New phone number for the lead', }, custom_fields: { type: 'object', description: 'Updated custom fields for the lead', }, }, required: ['lead_id'], }, }; export const UPDATE_LEAD_STATUS_TOOL: CategoryTool = { name: 'smartlead_update_lead_status', description: 'Update a lead\'s status.', category: ToolCategory.LEAD_MANAGEMENT, inputSchema: { type: 'object', properties: { lead_id: { type: 'number', description: 'ID of the lead to update', }, status: { type: 'string', description: 'New status for the lead', }, }, required: ['lead_id', 'status'], }, }; export const BULK_IMPORT_LEADS_TOOL: CategoryTool = { name: 'smartlead_bulk_import_leads', description: 'Import multiple leads into a campaign at once.', category: ToolCategory.LEAD_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to add the leads to', }, leads: { type: 'array', items: { type: 'object', properties: { email: { type: 'string', description: 'Email address of the lead', }, first_name: { type: 'string', description: 'First name of the lead', }, last_name: { type: 'string', description: 'Last name of the lead', }, company: { type: 'string', description: 'Company of the lead', }, title: { type: 'string', description: 'Job title of the lead', }, phone: { type: 'string', description: 'Phone number of the lead', }, custom_fields: { type: 'object', description: 'Custom fields for the lead', }, }, required: ['email'], }, description: 'Array of leads to import', }, }, required: ['campaign_id', 'leads'], }, }; export const DELETE_LEAD_TOOL: CategoryTool = { name: 'smartlead_delete_lead', description: 'Delete a lead permanently.', category: ToolCategory.LEAD_MANAGEMENT, inputSchema: { type: 'object', properties: { lead_id: { type: 'number', description: 'ID of the lead to delete', }, }, required: ['lead_id'], }, }; // Export an array of all lead management tools for registration export const leadTools = [ LIST_LEADS_TOOL, GET_LEAD_TOOL, ADD_LEAD_TO_CAMPAIGN_TOOL, UPDATE_LEAD_TOOL, UPDATE_LEAD_STATUS_TOOL, BULK_IMPORT_LEADS_TOOL, DELETE_LEAD_TOOL, ]; ``` -------------------------------------------------------------------------------- /src/handlers/smartSenders.ts: -------------------------------------------------------------------------------- ```typescript import { AxiosInstance } from 'axios'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { isGetVendorsParams, isSearchDomainParams, isAutoGenerateMailboxesParams, isPlaceOrderParams, isGetDomainListParams } from '../types/smartSenders.js'; // Smart Senders API base URL - different from the main SmartLead API const SMART_SENDERS_API_URL = 'https://smart-senders.smartlead.ai/api/v1'; // Handler for Smart Senders-related tools export async function handleSmartSendersTool( toolName: string, args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { switch (toolName) { case 'smartlead_get_vendors': { return handleGetVendors(args, apiClient, withRetry); } case 'smartlead_search_domain': { return handleSearchDomain(args, apiClient, withRetry); } case 'smartlead_auto_generate_mailboxes': { return handleAutoGenerateMailboxes(args, apiClient, withRetry); } case 'smartlead_place_order_mailboxes': { return handlePlaceOrderMailboxes(args, apiClient, withRetry); } case 'smartlead_get_domain_list': { return handleGetDomainList(args, apiClient, withRetry); } default: throw new Error(`Unknown Smart Senders tool: ${toolName}`); } } // Create a modified client for Smart Senders API with the correct base URL function createSmartSendersClient(apiClient: AxiosInstance) { return { get: (url: string, config?: any) => apiClient.get(`${SMART_SENDERS_API_URL}${url}`, config), post: (url: string, data?: any, config?: any) => apiClient.post(`${SMART_SENDERS_API_URL}${url}`, data, config), put: (url: string, data?: any, config?: any) => apiClient.put(`${SMART_SENDERS_API_URL}${url}`, data, config), delete: (url: string, config?: any) => apiClient.delete(`${SMART_SENDERS_API_URL}${url}`, config) }; } // Individual handlers for each tool async function handleGetVendors( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isGetVendorsParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_get_vendors' ); } try { const smartSendersClient = createSmartSendersClient(apiClient); const response = await withRetry( async () => smartSendersClient.get('/smart-senders/get-vendors'), 'get vendors' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleSearchDomain( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isSearchDomainParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_search_domain' ); } try { const smartSendersClient = createSmartSendersClient(apiClient); const { domain_name, vendor_id } = args; const response = await withRetry( async () => smartSendersClient.get(`/smart-senders/search-domain?domain_name=${domain_name}&vendor_id=${vendor_id}`), 'search domain' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleAutoGenerateMailboxes( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isAutoGenerateMailboxesParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_auto_generate_mailboxes' ); } try { const smartSendersClient = createSmartSendersClient(apiClient); const response = await withRetry( async () => smartSendersClient.post('/smart-senders/auto-generate-mailboxes', args), 'auto-generate mailboxes' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handlePlaceOrderMailboxes( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isPlaceOrderParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_place_order_mailboxes' ); } try { const smartSendersClient = createSmartSendersClient(apiClient); const response = await withRetry( async () => smartSendersClient.post('/smart-senders/place-order', args), 'place order for mailboxes' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleGetDomainList( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isGetDomainListParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_get_domain_list' ); } try { const smartSendersClient = createSmartSendersClient(apiClient); const response = await withRetry( async () => smartSendersClient.get('/smart-senders/get-domain-list'), 'get domain list' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } ``` -------------------------------------------------------------------------------- /src/types/email.ts: -------------------------------------------------------------------------------- ```typescript import { AxiosError } from 'axios'; // Type guards for Email Account parameters export function isListEmailAccountsParams(args: unknown): args is ListEmailAccountsParams { if (!args || typeof args !== 'object') return false; return true; } export function isAddEmailToCampaignParams(args: unknown): args is AddEmailToCampaignParams { if (!args || typeof args !== 'object') return false; const { campaign_id, email_account_id } = args as Partial<AddEmailToCampaignParams>; return ( typeof campaign_id === 'number' && typeof email_account_id === 'number' ); } export function isRemoveEmailFromCampaignParams(args: unknown): args is RemoveEmailFromCampaignParams { if (!args || typeof args !== 'object') return false; const { campaign_id, email_account_id } = args as Partial<RemoveEmailFromCampaignParams>; return ( typeof campaign_id === 'number' && typeof email_account_id === 'number' ); } export function isFetchEmailAccountsParams(args: unknown): args is FetchEmailAccountsParams { if (!args || typeof args !== 'object') return false; return true; } export function isCreateEmailAccountParams(args: unknown): args is CreateEmailAccountParams { if (!args || typeof args !== 'object') return false; const { from_name, from_email, user_name, password, smtp_host, smtp_port, imap_host, imap_port } = args as Partial<CreateEmailAccountParams>; return ( typeof from_name === 'string' && typeof from_email === 'string' && typeof user_name === 'string' && typeof password === 'string' && typeof smtp_host === 'string' && typeof smtp_port === 'number' && typeof imap_host === 'string' && typeof imap_port === 'number' ); } export function isUpdateEmailAccountParams(args: unknown): args is UpdateEmailAccountParams { if (!args || typeof args !== 'object') return false; const { email_account_id } = args as Partial<UpdateEmailAccountParams>; return typeof email_account_id === 'number'; } export function isFetchEmailAccountByIdParams(args: unknown): args is FetchEmailAccountByIdParams { if (!args || typeof args !== 'object') return false; const { email_account_id } = args as Partial<FetchEmailAccountByIdParams>; return typeof email_account_id === 'number'; } export function isUpdateEmailWarmupParams(args: unknown): args is UpdateEmailWarmupParams { if (!args || typeof args !== 'object') return false; const { email_account_id, warmup_enabled } = args as Partial<UpdateEmailWarmupParams>; return ( typeof email_account_id === 'number' && typeof warmup_enabled === 'string' ); } export function isReconnectEmailAccountParams(args: unknown): args is ReconnectEmailAccountParams { if (!args || typeof args !== 'object') return false; const { email_account_id } = args as Partial<ReconnectEmailAccountParams>; return typeof email_account_id === 'number'; } export function isUpdateEmailAccountTagParams(args: unknown): args is UpdateEmailAccountTagParams { if (!args || typeof args !== 'object') return false; const { id, name, color } = args as Partial<UpdateEmailAccountTagParams>; return ( typeof id === 'number' && typeof name === 'string' && typeof color === 'string' ); } // Interface definitions for Email Account parameters export interface ListEmailAccountsParams { campaign_id?: number; status?: string; limit?: number; offset?: number; } export interface AddEmailToCampaignParams { campaign_id: number; email_account_id: number; } export interface RemoveEmailFromCampaignParams { campaign_id: number; email_account_id: number; } export interface FetchEmailAccountsParams { status?: string; limit?: number; offset?: number; username?: string; client_id?: number; // Required Client ID according to the docs } export interface CreateEmailAccountParams { from_name: string; // User's name from_email: string; // User email user_name: string; // Username password: string; // User's password smtp_host: string; // Mail SMTP host smtp_port: number; // Mail SMTP port imap_host: string; // Imap host URL imap_port: number; // Imap port max_email_per_day?: number; // Max number of emails per day custom_tracking_url?: string; // Custom email tracking url bcc?: string; // Email BCC signature?: string; // Email signature warmup_enabled?: boolean; // Set true to enable warmup total_warmup_per_day?: number; // Total number of warmups per day daily_rampup?: number; // Daily rampup number reply_rate_percentage?: number; // Reply rate in percentage client_id?: number; // Client ID } export interface UpdateEmailAccountParams { email_account_id: number; // ID of the email to update max_email_per_day?: number; // Max number of emails per day custom_tracking_url?: string; // Custom email tracking URL bcc?: string; // Email BCC signature?: string; // Email signature client_id?: number | null; // Client ID. Set to null if not needed time_to_wait_in_mins?: number; // Minimum integer time (in minutes) to wait before sending next email } export interface FetchEmailAccountByIdParams { email_account_id: number; } export interface UpdateEmailWarmupParams { email_account_id: number; // Email account ID warmup_enabled: string; // Set false to disable warmup total_warmup_per_day?: number; // Total number of warmups in a day daily_rampup?: number; // Set this value to increase or decrease daily ramup in warmup emails reply_rate_percentage?: string; // Reply rate in percentage warmup_key_id?: string; // If passed will update the custom warmup-key identifier } export interface ReconnectEmailAccountParams { email_account_id: number; connection_details?: { smtp_host?: string; smtp_port?: number; smtp_username?: string; smtp_password?: string; imap_host?: string; imap_port?: number; imap_username?: string; imap_password?: string; oauth_token?: string; }; } export interface UpdateEmailAccountTagParams { id: number; // ID of the tag name: string; // Name of the tag color: string; // The color of the tag in HEX format } // Email Account response interfaces export interface EmailAccount { id: number; email: string; name?: string; provider: string; status: string; created_at: string; updated_at: string; last_checked_at?: string; warmup_enabled: boolean; daily_limit?: number; tags?: string[]; } export interface EmailAccountResponse { success: boolean; data: EmailAccount; message?: string; } export interface EmailAccountListResponse { success: boolean; data: { accounts: EmailAccount[]; total: number; }; message?: string; } export interface EmailAccountActionResponse { success: boolean; message: string; } ``` -------------------------------------------------------------------------------- /src/types/statistics.ts: -------------------------------------------------------------------------------- ```typescript import { CategoryTool, ToolCategory } from './common.js'; // Interface for fetching campaign statistics export interface CampaignStatisticsParams { campaign_id: number; offset?: number; limit?: number; email_sequence_number?: string; email_status?: string; sent_time_start_date?: string; sent_time_end_date?: string; } // Interface for fetching campaign statistics by date range export interface CampaignStatisticsByDateParams { campaign_id: number; start_date: string; end_date: string; } // Interface for fetching warmup stats by email account export interface WarmupStatsByEmailParams { email_account_id: number; } // Interface for fetching campaign top level analytics export interface CampaignTopLevelAnalyticsParams { campaign_id: number; } // Interface for fetching campaign top level analytics by date range export interface CampaignTopLevelAnalyticsByDateParams { campaign_id: number; start_date: string; end_date: string; } // Interface for fetching campaign lead statistics export interface CampaignLeadStatisticsParams { campaign_id: number; limit?: number; created_at_gt?: string; event_time_gt?: string; offset?: number; } // Interface for fetching campaign mailbox statistics export interface CampaignMailboxStatisticsParams { campaign_id: number; client_id?: string; offset?: number; limit?: number; start_date?: string; end_date?: string; timezone?: string; } // Interface for downloading campaign data export interface DownloadCampaignDataParams { campaign_id: number; download_type: string; format: string; user_id?: string; } // Interface for viewing download statistics export interface ViewDownloadStatisticsParams { time_period?: 'all' | 'today' | 'week' | 'month'; group_by?: 'type' | 'format' | 'campaign' | 'date'; } // Type guards for params validation export function isCampaignStatisticsParams(args: unknown): args is CampaignStatisticsParams { if (typeof args !== 'object' || args === null) { return false; } const params = args as CampaignStatisticsParams; if (typeof params.campaign_id !== 'number') { return false; } // Optional offset must be a number if present if (params.offset !== undefined && typeof params.offset !== 'number') { return false; } // Optional limit must be a number if present if (params.limit !== undefined && typeof params.limit !== 'number') { return false; } // Optional email_sequence_number must be a string if present if (params.email_sequence_number !== undefined && typeof params.email_sequence_number !== 'string') { return false; } // Optional email_status must be a string if present if (params.email_status !== undefined && typeof params.email_status !== 'string') { return false; } // Optional sent_time_start_date must be a string if present if (params.sent_time_start_date !== undefined && typeof params.sent_time_start_date !== 'string') { return false; } // Optional sent_time_end_date must be a string if present if (params.sent_time_end_date !== undefined && typeof params.sent_time_end_date !== 'string') { return false; } return true; } export function isCampaignStatisticsByDateParams(args: unknown): args is CampaignStatisticsByDateParams { if (typeof args !== 'object' || args === null) { return false; } const params = args as CampaignStatisticsByDateParams; return ( typeof params.campaign_id === 'number' && typeof params.start_date === 'string' && typeof params.end_date === 'string' ); } export function isWarmupStatsByEmailParams(args: unknown): args is WarmupStatsByEmailParams { if (typeof args !== 'object' || args === null) { return false; } const params = args as WarmupStatsByEmailParams; return typeof params.email_account_id === 'number'; } export function isCampaignTopLevelAnalyticsParams(args: unknown): args is CampaignTopLevelAnalyticsParams { if (typeof args !== 'object' || args === null) { return false; } const params = args as CampaignTopLevelAnalyticsParams; return typeof params.campaign_id === 'number'; } export function isCampaignTopLevelAnalyticsByDateParams(args: unknown): args is CampaignTopLevelAnalyticsByDateParams { if (typeof args !== 'object' || args === null) { return false; } const params = args as CampaignTopLevelAnalyticsByDateParams; return ( typeof params.campaign_id === 'number' && typeof params.start_date === 'string' && typeof params.end_date === 'string' ); } export function isCampaignLeadStatisticsParams(args: unknown): args is CampaignLeadStatisticsParams { if (typeof args !== 'object' || args === null) { return false; } const params = args as CampaignLeadStatisticsParams; if (typeof params.campaign_id !== 'number') { return false; } // Optional limit must be a string if present (it will be converted to number) if (params.limit !== undefined && typeof params.limit !== 'number') { return false; } // Optional created_at_gt must be a string if present if (params.created_at_gt !== undefined && typeof params.created_at_gt !== 'string') { return false; } // Optional event_time_gt must be a string if present if (params.event_time_gt !== undefined && typeof params.event_time_gt !== 'string') { return false; } // Optional offset must be a string if present (it will be converted to number) if (params.offset !== undefined && typeof params.offset !== 'number') { return false; } return true; } export function isCampaignMailboxStatisticsParams(obj: unknown): obj is CampaignMailboxStatisticsParams { return ( !!obj && typeof obj === 'object' && 'campaign_id' in obj && typeof (obj as CampaignMailboxStatisticsParams).campaign_id === 'number' ); } export function isDownloadCampaignDataParams(obj: unknown): obj is DownloadCampaignDataParams { if (!obj || typeof obj !== 'object') return false; const params = obj as Partial<DownloadCampaignDataParams>; // Check required fields if (typeof params.campaign_id !== 'number') return false; if (!params.download_type || !['analytics', 'leads', 'sequence', 'full_export'].includes(params.download_type)) { return false; } if (!params.format || !['json', 'csv'].includes(params.format)) { return false; } // Check optional fields if (params.user_id !== undefined && typeof params.user_id !== 'string') { return false; } return true; } export function isViewDownloadStatisticsParams(obj: unknown): obj is ViewDownloadStatisticsParams { if (!obj || typeof obj !== 'object') return false; const params = obj as Partial<ViewDownloadStatisticsParams>; // Both fields are optional, but need to be validated if present if (params.time_period !== undefined && !['all', 'today', 'week', 'month'].includes(params.time_period)) { return false; } if (params.group_by !== undefined && !['type', 'format', 'campaign', 'date'].includes(params.group_by)) { return false; } return true; } ``` -------------------------------------------------------------------------------- /src/types/lead.ts: -------------------------------------------------------------------------------- ```typescript import { CategoryTool, ToolCategory } from './common.js'; // Interface for listing leads export interface ListLeadsParams { campaign_id?: number; status?: string; limit?: number; offset?: number; search?: string; start_date?: string; end_date?: string; } // Interface for getting a single lead export interface GetLeadParams { lead_id: number; } // Interface for adding a lead to a campaign export interface AddLeadToCampaignParams { campaign_id: number; email: string; first_name?: string; last_name?: string; company?: string; title?: string; phone?: string; custom_fields?: Record<string, string>; } // Interface for updating a lead export interface UpdateLeadParams { lead_id: number; email?: string; first_name?: string; last_name?: string; company?: string; title?: string; phone?: string; custom_fields?: Record<string, string>; } // Interface for updating lead status export interface UpdateLeadStatusParams { lead_id: number; status: string; } // Interface for bulk importing leads export interface BulkImportLeadsParams { campaign_id: number; leads: Array<{ email: string; first_name?: string; last_name?: string; company?: string; title?: string; phone?: string; custom_fields?: Record<string, string>; }>; } // Interface for deleting a lead export interface DeleteLeadParams { lead_id: number; } // Type guards for params validation export function isListLeadsParams(args: unknown): args is ListLeadsParams { if (typeof args !== 'object' || args === null) { return false; } const params = args as ListLeadsParams; // Optional campaign_id must be a number if present if (params.campaign_id !== undefined && typeof params.campaign_id !== 'number') { return false; } // Optional status must be a string if present if (params.status !== undefined && typeof params.status !== 'string') { return false; } // Optional limit must be a number if present if (params.limit !== undefined && typeof params.limit !== 'number') { return false; } // Optional offset must be a number if present if (params.offset !== undefined && typeof params.offset !== 'number') { return false; } // Optional search must be a string if present if (params.search !== undefined && typeof params.search !== 'string') { return false; } // Optional start_date must be a string if present if (params.start_date !== undefined && typeof params.start_date !== 'string') { return false; } // Optional end_date must be a string if present if (params.end_date !== undefined && typeof params.end_date !== 'string') { return false; } return true; } export function isGetLeadParams(args: unknown): args is GetLeadParams { return ( typeof args === 'object' && args !== null && 'lead_id' in args && typeof (args as { lead_id: unknown }).lead_id === 'number' ); } export function isAddLeadToCampaignParams(args: unknown): args is AddLeadToCampaignParams { if ( typeof args !== 'object' || args === null || !('campaign_id' in args) || !('email' in args) || typeof (args as { campaign_id: unknown }).campaign_id !== 'number' || typeof (args as { email: unknown }).email !== 'string' ) { return false; } const params = args as AddLeadToCampaignParams; // Optional fields validation if (params.first_name !== undefined && typeof params.first_name !== 'string') { return false; } if (params.last_name !== undefined && typeof params.last_name !== 'string') { return false; } if (params.company !== undefined && typeof params.company !== 'string') { return false; } if (params.title !== undefined && typeof params.title !== 'string') { return false; } if (params.phone !== undefined && typeof params.phone !== 'string') { return false; } if ( params.custom_fields !== undefined && (typeof params.custom_fields !== 'object' || params.custom_fields === null) ) { return false; } return true; } export function isUpdateLeadParams(args: unknown): args is UpdateLeadParams { if ( typeof args !== 'object' || args === null || !('lead_id' in args) || typeof (args as { lead_id: unknown }).lead_id !== 'number' ) { return false; } const params = args as UpdateLeadParams; // Optional fields validation if (params.email !== undefined && typeof params.email !== 'string') { return false; } if (params.first_name !== undefined && typeof params.first_name !== 'string') { return false; } if (params.last_name !== undefined && typeof params.last_name !== 'string') { return false; } if (params.company !== undefined && typeof params.company !== 'string') { return false; } if (params.title !== undefined && typeof params.title !== 'string') { return false; } if (params.phone !== undefined && typeof params.phone !== 'string') { return false; } if ( params.custom_fields !== undefined && (typeof params.custom_fields !== 'object' || params.custom_fields === null) ) { return false; } return true; } export function isUpdateLeadStatusParams(args: unknown): args is UpdateLeadStatusParams { return ( typeof args === 'object' && args !== null && 'lead_id' in args && 'status' in args && typeof (args as { lead_id: unknown }).lead_id === 'number' && typeof (args as { status: unknown }).status === 'string' ); } export function isBulkImportLeadsParams(args: unknown): args is BulkImportLeadsParams { if ( typeof args !== 'object' || args === null || !('campaign_id' in args) || !('leads' in args) || typeof (args as { campaign_id: unknown }).campaign_id !== 'number' || !Array.isArray((args as { leads: unknown }).leads) ) { return false; } const params = args as BulkImportLeadsParams; // Validate each lead in the leads array for (const lead of params.leads) { if ( typeof lead !== 'object' || lead === null || !('email' in lead) || typeof lead.email !== 'string' ) { return false; } // Optional fields validation if (lead.first_name !== undefined && typeof lead.first_name !== 'string') { return false; } if (lead.last_name !== undefined && typeof lead.last_name !== 'string') { return false; } if (lead.company !== undefined && typeof lead.company !== 'string') { return false; } if (lead.title !== undefined && typeof lead.title !== 'string') { return false; } if (lead.phone !== undefined && typeof lead.phone !== 'string') { return false; } if ( lead.custom_fields !== undefined && (typeof lead.custom_fields !== 'object' || lead.custom_fields === null) ) { return false; } } return true; } export function isDeleteLeadParams(args: unknown): args is DeleteLeadParams { return ( typeof args === 'object' && args !== null && 'lead_id' in args && typeof (args as { lead_id: unknown }).lead_id === 'number' ); } ``` -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { program } from 'commander'; import dotenv from 'dotenv'; import { spawn } from 'child_process'; import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; import readline from 'readline'; import { createInterface } from 'readline'; // Load environment variables from .env file dotenv.config(); // Get the directory name of the current module const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Get version from package.json const version = '1.0.0'; // This should ideally be imported from package.json // License server URL const LICENSE_SERVER_URL = 'https://sea-turtle-app-64etr.ondigitalocean.app/'; // Function to prompt for value interactively async function promptForValue(question: string, hidden = false): Promise<string> { const rl = createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { if (hidden) { process.stdout.write(question); process.stdin.setRawMode(true); let password = ''; process.stdin.on('data', (chunk) => { const str = chunk.toString(); if (str === '\n' || str === '\r' || str === '\u0004') { process.stdin.setRawMode(false); process.stdout.write('\n'); rl.close(); resolve(password); } else if (str === '\u0003') { // Ctrl+C process.exit(0); } else if (str === '\u007F') { // Backspace if (password.length > 0) { password = password.substring(0, password.length - 1); process.stdout.write('\b \b'); } } else { password += str; process.stdout.write('*'); } }); } else { rl.question(question, (answer) => { rl.close(); resolve(answer); }); } }); } // Function to ensure required environment variables are set async function ensureEnvVars(): Promise<void> { // Check for API key if (!process.env.SMARTLEAD_API_KEY) { console.log('\nSmartlead API Key not found.'); const apiKey = await promptForValue('Enter your Smartlead API Key: ', true); if (apiKey) { process.env.SMARTLEAD_API_KEY = apiKey; } else { console.log('Smartlead API Key is required to continue.'); process.exit(1); } } // Check for license key if (!process.env.JEAN_LICENSE_KEY) { console.log('\nJean License Key not found. Defaulting to free tier access.'); process.env.JEAN_LICENSE_KEY = 'JEANPARTNER'; } // Set license server URL if not set if (!process.env.LICENSE_SERVER_URL) { process.env.LICENSE_SERVER_URL = LICENSE_SERVER_URL; } console.log('\nConfiguration complete!\n'); } // Function to save environment variables to .env file async function saveEnvToFile(): Promise<void> { if (process.env.SMARTLEAD_API_KEY || process.env.JEAN_LICENSE_KEY) { const saveEnv = await promptForValue('Do you want to save these settings to a .env file for future use? (y/n): '); if (saveEnv.toLowerCase() === 'y') { try { let envContent = ''; if (process.env.SMARTLEAD_API_KEY) { envContent += `SMARTLEAD_API_KEY=${process.env.SMARTLEAD_API_KEY}\n`; } if (process.env.JEAN_LICENSE_KEY) { envContent += `JEAN_LICENSE_KEY=${process.env.JEAN_LICENSE_KEY}\n`; } if (process.env.LICENSE_SERVER_URL) { envContent += `LICENSE_SERVER_URL=${process.env.LICENSE_SERVER_URL}\n`; } fs.writeFileSync('.env', envContent); console.log('Settings saved to .env file in the current directory.'); } catch (error) { console.error('Error saving .env file:', error); } } } } program .version(version) .description('Smartlead MCP Server CLI'); program .command('start') .description('Start the MCP server in standard STDIO mode') .option('--api-key <key>', 'Your Smartlead API Key') .option('--license-key <key>', 'Your Jean License Key') .action(async (options: { apiKey?: string; licenseKey?: string }) => { // Set env vars from command line options if provided if (options.apiKey) process.env.SMARTLEAD_API_KEY = options.apiKey; if (options.licenseKey) process.env.JEAN_LICENSE_KEY = options.licenseKey; // Ensure required env vars are set (will prompt if missing) await ensureEnvVars(); await saveEnvToFile(); console.log('Starting Smartlead MCP Server in STDIO mode...'); // Run as a separate process instead of trying to import const indexPath = path.join(__dirname, 'index.js'); const child = spawn('node', [indexPath], { stdio: 'inherit', env: process.env }); process.on('SIGINT', () => { child.kill(); process.exit(); }); }); program .command('sse') .description('Start the MCP server in SSE mode for n8n integration') .option('-p, --port <port>', 'Port to run the server on', '3000') .option('--api-key <key>', 'Your Smartlead API Key') .option('--license-key <key>', 'Your Jean License Key') .action(async (options: { port: string; apiKey?: string; licenseKey?: string }) => { // Set env vars from command line options if provided if (options.apiKey) process.env.SMARTLEAD_API_KEY = options.apiKey; if (options.licenseKey) process.env.JEAN_LICENSE_KEY = options.licenseKey; // Ensure required env vars are set (will prompt if missing) await ensureEnvVars(); await saveEnvToFile(); console.log(`Starting Smartlead MCP Server in SSE mode on port ${options.port}...`); console.log(`Connect from n8n to http://localhost:${options.port}/sse`); // Use supergateway to run in SSE mode const indexPath = path.join(__dirname, 'index.js'); const supergateway = spawn('npx', [ '-y', 'supergateway', '--stdio', `node ${indexPath}`, '--port', options.port ], { shell: true, stdio: 'inherit', env: process.env }); process.on('SIGINT', () => { supergateway.kill(); process.exit(); }); }); program .command('config') .description('Show current configuration and set up environment variables') .option('--api-key <key>', 'Set your Smartlead API Key') .option('--license-key <key>', 'Set your Jean License Key') .action(async (options: { apiKey?: string; licenseKey?: string }) => { if (options.apiKey) process.env.SMARTLEAD_API_KEY = options.apiKey; if (options.licenseKey) process.env.JEAN_LICENSE_KEY = options.licenseKey; await ensureEnvVars(); await saveEnvToFile(); console.log('\nSmartlead MCP Server Configuration:'); console.log(`API URL: ${process.env.SMARTLEAD_API_URL || 'https://server.smartlead.ai/api/v1'}`); console.log(`License Server: ${process.env.LICENSE_SERVER_URL || LICENSE_SERVER_URL}`); console.log(`License Status: ${process.env.JEAN_LICENSE_KEY ? 'Configured' : 'Not Configured'}`); console.log('\nConfiguration saved and ready to use.'); }); program.parse(process.argv); // Default to help if no command is provided if (!process.argv.slice(2).length) { program.outputHelp(); } ``` -------------------------------------------------------------------------------- /src/handlers/webhooks.ts: -------------------------------------------------------------------------------- ```typescript import { AxiosInstance } from 'axios'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { isFetchWebhooksByCampaignParams, isUpsertCampaignWebhookParams, isDeleteCampaignWebhookParams, isGetWebhooksPublishSummaryParams, isRetriggerFailedEventsParams } from '../types/webhooks.js'; // SmartLead API base URL const SMARTLEAD_API_URL = 'https://server.smartlead.ai/api/v1'; // Handler for Webhook-related tools export async function handleWebhookTool( toolName: string, args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { switch (toolName) { case 'smartlead_fetch_webhooks_by_campaign': { return handleFetchWebhooksByCampaign(args, apiClient, withRetry); } case 'smartlead_upsert_campaign_webhook': { return handleUpsertCampaignWebhook(args, apiClient, withRetry); } case 'smartlead_delete_campaign_webhook': { return handleDeleteCampaignWebhook(args, apiClient, withRetry); } case 'smartlead_get_webhooks_publish_summary': { return handleGetWebhooksPublishSummary(args, apiClient, withRetry); } case 'smartlead_retrigger_failed_events': { return handleRetriggerFailedEvents(args, apiClient, withRetry); } default: throw new Error(`Unknown Webhook tool: ${toolName}`); } } // Create a modified client for SmartLead API with the correct base URL function createSmartLeadClient(apiClient: AxiosInstance) { return { get: (url: string, config?: any) => apiClient.get(`${SMARTLEAD_API_URL}${url}`, config), post: (url: string, data?: any, config?: any) => apiClient.post(`${SMARTLEAD_API_URL}${url}`, data, config), put: (url: string, data?: any, config?: any) => apiClient.put(`${SMARTLEAD_API_URL}${url}`, data, config), delete: (url: string, config?: any) => apiClient.delete(`${SMARTLEAD_API_URL}${url}`, config) }; } // Individual handlers for each tool async function handleFetchWebhooksByCampaign( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isFetchWebhooksByCampaignParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_fetch_webhooks_by_campaign' ); } try { const smartLeadClient = createSmartLeadClient(apiClient); const { campaign_id } = args; const response = await withRetry( async () => smartLeadClient.get(`/campaigns/${campaign_id}/webhooks`), 'fetch webhooks by campaign' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleUpsertCampaignWebhook( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isUpsertCampaignWebhookParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_upsert_campaign_webhook' ); } try { const smartLeadClient = createSmartLeadClient(apiClient); const { campaign_id, ...webhookData } = args; const response = await withRetry( async () => smartLeadClient.post(`/campaigns/${campaign_id}/webhooks`, webhookData), 'upsert campaign webhook' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleDeleteCampaignWebhook( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isDeleteCampaignWebhookParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_delete_campaign_webhook' ); } try { const smartLeadClient = createSmartLeadClient(apiClient); const { campaign_id, id } = args; // The API documentation suggests a DELETE with a body payload // Different from typical REST practices but following the API spec const response = await withRetry( async () => smartLeadClient.delete(`/campaigns/${campaign_id}/webhooks`, { data: { id } }), 'delete campaign webhook' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleGetWebhooksPublishSummary( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isGetWebhooksPublishSummaryParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_get_webhooks_publish_summary' ); } try { const smartLeadClient = createSmartLeadClient(apiClient); const { campaign_id, fromTime, toTime } = args; let url = `/campaigns/${campaign_id}/webhooks/summary`; const queryParams = new URLSearchParams(); if (fromTime) { queryParams.append('fromTime', fromTime); } if (toTime) { queryParams.append('toTime', toTime); } if (queryParams.toString()) { url += `?${queryParams.toString()}`; } const response = await withRetry( async () => smartLeadClient.get(url), 'get webhooks publish summary' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleRetriggerFailedEvents( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isRetriggerFailedEventsParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_retrigger_failed_events' ); } try { const smartLeadClient = createSmartLeadClient(apiClient); const { campaign_id, fromTime, toTime } = args; const response = await withRetry( async () => smartLeadClient.post(`/campaigns/${campaign_id}/webhooks/retrigger-failed-events`, { fromTime, toTime }), 'retrigger failed events' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } ``` -------------------------------------------------------------------------------- /src/types/campaign.ts: -------------------------------------------------------------------------------- ```typescript // Type definitions for campaign management export interface CreateCampaignParams { name: string; client_id?: number; } export interface UpdateCampaignScheduleParams { campaign_id: number; timezone?: string; days_of_the_week?: number[]; start_hour?: string; end_hour?: string; min_time_btw_emails?: number; max_new_leads_per_day?: number; schedule_start_time?: string; } export interface UpdateCampaignSettingsParams { campaign_id: number; name?: string; status?: 'active' | 'paused' | 'completed'; settings?: Record<string, any>; } export interface UpdateCampaignStatusParams { campaign_id: number; status: 'PAUSED' | 'STOPPED' | 'START'; } export interface GetCampaignParams { campaign_id: number; } export interface GetCampaignSequenceParams { campaign_id: number; } export interface ListCampaignsParams { status?: 'active' | 'paused' | 'completed'; limit?: number; offset?: number; } export interface SaveCampaignSequenceParams { campaign_id: number; sequence: Array<{ seq_number: number; seq_delay_details: { delay_in_days: number; }; variant_distribution_type: 'MANUAL_EQUAL' | 'MANUAL_PERCENTAGE' | 'AI_EQUAL'; lead_distribution_percentage?: number; winning_metric_property?: 'OPEN_RATE' | 'CLICK_RATE' | 'REPLY_RATE' | 'POSITIVE_REPLY_RATE'; seq_variants: Array<{ subject: string; email_body: string; variant_label: string; id?: number; variant_distribution_percentage?: number; }>; }>; } // New interface definitions for remaining campaign management endpoints export interface GetCampaignsByLeadParams { lead_id: number; } export interface ExportCampaignLeadsParams { campaign_id: number; } export interface DeleteCampaignParams { campaign_id: number; } export interface GetCampaignAnalyticsByDateParams { campaign_id: number; start_date: string; end_date: string; } export interface GetCampaignSequenceAnalyticsParams { campaign_id: number; start_date: string; end_date: string; time_zone?: string; } // Type guards export function isCreateCampaignParams(args: unknown): args is CreateCampaignParams { return ( typeof args === 'object' && args !== null && 'name' in args && typeof (args as { name: unknown }).name === 'string' ); } export function isUpdateCampaignScheduleParams(args: unknown): args is UpdateCampaignScheduleParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as { campaign_id: unknown }).campaign_id === 'number' ); } export function isUpdateCampaignSettingsParams(args: unknown): args is UpdateCampaignSettingsParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as { campaign_id: unknown }).campaign_id === 'number' ); } export function isUpdateCampaignStatusParams(args: unknown): args is UpdateCampaignStatusParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as { campaign_id: unknown }).campaign_id === 'number' && 'status' in args && typeof (args as { status: unknown }).status === 'string' && ['PAUSED', 'STOPPED', 'START'].includes((args as { status: string }).status) ); } export function isGetCampaignParams(args: unknown): args is GetCampaignParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as { campaign_id: unknown }).campaign_id === 'number' ); } export function isGetCampaignSequenceParams(args: unknown): args is GetCampaignSequenceParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as { campaign_id: unknown }).campaign_id === 'number' ); } export function isListCampaignsParams(args: unknown): args is ListCampaignsParams { return typeof args === 'object' && args !== null; } export function isSaveCampaignSequenceParams(args: unknown): args is SaveCampaignSequenceParams { if ( typeof args !== 'object' || args === null || !('campaign_id' in args) || typeof (args as { campaign_id: unknown }).campaign_id !== 'number' || !('sequence' in args) || !Array.isArray((args as { sequence: unknown }).sequence) ) { return false; } const sequence = (args as { sequence: unknown[] }).sequence; return sequence.every(isValidSequenceItem); } // New type guards for the remaining campaign management endpoints export function isGetCampaignsByLeadParams(args: unknown): args is GetCampaignsByLeadParams { return ( typeof args === 'object' && args !== null && 'lead_id' in args && typeof (args as { lead_id: unknown }).lead_id === 'number' ); } export function isExportCampaignLeadsParams(args: unknown): args is ExportCampaignLeadsParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as { campaign_id: unknown }).campaign_id === 'number' ); } export function isDeleteCampaignParams(args: unknown): args is DeleteCampaignParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as { campaign_id: unknown }).campaign_id === 'number' ); } export function isGetCampaignAnalyticsByDateParams(args: unknown): args is GetCampaignAnalyticsByDateParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as { campaign_id: unknown }).campaign_id === 'number' && 'start_date' in args && typeof (args as { start_date: unknown }).start_date === 'string' && 'end_date' in args && typeof (args as { end_date: unknown }).end_date === 'string' ); } export function isGetCampaignSequenceAnalyticsParams(args: unknown): args is GetCampaignSequenceAnalyticsParams { return ( typeof args === 'object' && args !== null && 'campaign_id' in args && typeof (args as { campaign_id: unknown }).campaign_id === 'number' && 'start_date' in args && typeof (args as { start_date: unknown }).start_date === 'string' && 'end_date' in args && typeof (args as { end_date: unknown }).end_date === 'string' ); } function isValidSequenceItem(item: unknown): boolean { return ( typeof item === 'object' && item !== null && 'seq_number' in item && typeof (item as { seq_number: unknown }).seq_number === 'number' && 'seq_delay_details' in item && typeof (item as { seq_delay_details: unknown }).seq_delay_details === 'object' && (item as { seq_delay_details: unknown }).seq_delay_details !== null && 'delay_in_days' in (item as { seq_delay_details: { delay_in_days: unknown } }).seq_delay_details && typeof (item as { seq_delay_details: { delay_in_days: unknown } }).seq_delay_details.delay_in_days === 'number' && 'variant_distribution_type' in item && typeof (item as { variant_distribution_type: unknown }).variant_distribution_type === 'string' && 'seq_variants' in item && Array.isArray((item as { seq_variants: unknown[] }).seq_variants) && (item as { seq_variants: unknown[] }).seq_variants.every( (variant) => typeof variant === 'object' && variant !== null && 'subject' in variant && typeof (variant as { subject: unknown }).subject === 'string' && 'email_body' in variant && typeof (variant as { email_body: unknown }).email_body === 'string' && 'variant_label' in variant && typeof (variant as { variant_label: unknown }).variant_label === 'string' ) ); } ``` -------------------------------------------------------------------------------- /src/tools/statistics.ts: -------------------------------------------------------------------------------- ```typescript import { CategoryTool, ToolCategory } from '../types/common.js'; // Campaign Statistics Tools export const CAMPAIGN_STATISTICS_TOOL: CategoryTool = { name: 'smartlead_get_campaign_statistics', description: 'Fetch campaign statistics using the campaign\'s ID.', category: ToolCategory.CAMPAIGN_STATISTICS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to fetch statistics for', }, offset: { type: 'number', description: 'Offset for pagination', }, limit: { type: 'number', description: 'Maximum number of statistics to return', }, email_sequence_number: { type: 'string', description: 'Email sequence number to filter by (e.g., "1,2,3,4")', }, email_status: { type: 'string', description: 'Email status to filter by (e.g., "opened", "clicked", "replied", "unsubscribed", "bounced")', }, sent_time_start_date: { type: 'string', description: 'Filter by sent time greater than this date (e.g., "2023-10-16 10:33:02.000Z")', }, sent_time_end_date: { type: 'string', description: 'Filter by sent time less than this date (e.g., "2023-10-16 10:33:02.000Z")', }, }, required: ['campaign_id'], }, }; export const CAMPAIGN_STATISTICS_BY_DATE_TOOL: CategoryTool = { name: 'smartlead_get_campaign_statistics_by_date', description: 'Fetch campaign statistics for a specific date range.', category: ToolCategory.CAMPAIGN_STATISTICS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to fetch statistics for', }, start_date: { type: 'string', description: 'Start date in YYYY-MM-DD format', }, end_date: { type: 'string', description: 'End date in YYYY-MM-DD format', }, }, required: ['campaign_id', 'start_date', 'end_date'], }, }; export const WARMUP_STATS_BY_EMAIL_TOOL: CategoryTool = { name: 'smartlead_get_warmup_stats_by_email', description: 'Fetch warmup stats for the last 7 days for a specific email account.', category: ToolCategory.CAMPAIGN_STATISTICS, inputSchema: { type: 'object', properties: { email_account_id: { type: 'number', description: 'ID of the email account to fetch warmup stats for', }, }, required: ['email_account_id'], }, }; export const CAMPAIGN_TOP_LEVEL_ANALYTICS_TOOL: CategoryTool = { name: 'smartlead_get_campaign_top_level_analytics', description: 'Fetch top level analytics for a campaign.', category: ToolCategory.CAMPAIGN_STATISTICS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to fetch analytics for', }, }, required: ['campaign_id'], }, }; export const CAMPAIGN_TOP_LEVEL_ANALYTICS_BY_DATE_TOOL: CategoryTool = { name: 'smartlead_get_campaign_top_level_analytics_by_date', description: 'Fetch campaign top level analytics for a specific date range.', category: ToolCategory.CAMPAIGN_STATISTICS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to fetch analytics for', }, start_date: { type: 'string', description: 'Start date in YYYY-MM-DD format', }, end_date: { type: 'string', description: 'End date in YYYY-MM-DD format', }, }, required: ['campaign_id', 'start_date', 'end_date'], }, }; export const CAMPAIGN_LEAD_STATISTICS_TOOL: CategoryTool = { name: 'smartlead_get_campaign_lead_statistics', description: 'Fetch lead statistics for a campaign.', category: ToolCategory.CAMPAIGN_STATISTICS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to fetch lead statistics for', }, limit: { type: 'number', description: 'Maximum number of leads to return (max 100)', }, created_at_gt: { type: 'string', description: 'Filter by leads created after this date (YYYY-MM-DD format)', }, event_time_gt: { type: 'string', description: 'Filter by events after this date (YYYY-MM-DD format)', }, offset: { type: 'number', description: 'Offset for pagination', }, }, required: ['campaign_id'], }, }; export const CAMPAIGN_MAILBOX_STATISTICS_TOOL: CategoryTool = { name: 'smartlead_get_campaign_mailbox_statistics', description: 'Fetch mailbox statistics for a campaign.', category: ToolCategory.CAMPAIGN_STATISTICS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to fetch mailbox statistics for', }, client_id: { type: 'string', description: 'Client ID if the campaign is client-specific', }, offset: { type: 'number', description: 'Offset for pagination', }, limit: { type: 'number', description: 'Maximum number of results to return (min 1, max 20)', }, start_date: { type: 'string', description: 'Start date (must be used with end_date)', }, end_date: { type: 'string', description: 'End date (must be used with start_date)', }, timezone: { type: 'string', description: 'Timezone for the data (e.g., "America/Los_Angeles")', }, }, required: ['campaign_id'], }, }; // Add this new tool for downloading campaign data with tracking export const DOWNLOAD_CAMPAIGN_DATA_TOOL: CategoryTool = { name: 'smartlead_download_campaign_data', description: 'Download campaign data and track the download for analytics.', category: ToolCategory.CAMPAIGN_STATISTICS, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to download data for', }, download_type: { type: 'string', enum: ['analytics', 'leads', 'sequence', 'full_export'], description: 'Type of data to download', }, format: { type: 'string', enum: ['json', 'csv'], description: 'Format of the downloaded data', }, user_id: { type: 'string', description: 'Optional user identifier for tracking downloads', }, }, required: ['campaign_id', 'download_type', 'format'], }, }; // Add this new tool for viewing download statistics export const VIEW_DOWNLOAD_STATISTICS_TOOL: CategoryTool = { name: 'smartlead_view_download_statistics', description: 'View statistics about downloaded campaign data.', category: ToolCategory.CAMPAIGN_STATISTICS, inputSchema: { type: 'object', properties: { time_period: { type: 'string', enum: ['all', 'today', 'week', 'month'], description: 'Time period to filter statistics by', }, group_by: { type: 'string', enum: ['type', 'format', 'campaign', 'date'], description: 'How to group the statistics', }, }, }, }; // Export an array of all campaign statistics tools for registration export const statisticsTools: CategoryTool[] = [ CAMPAIGN_STATISTICS_TOOL, CAMPAIGN_STATISTICS_BY_DATE_TOOL, WARMUP_STATS_BY_EMAIL_TOOL, CAMPAIGN_TOP_LEVEL_ANALYTICS_TOOL, CAMPAIGN_TOP_LEVEL_ANALYTICS_BY_DATE_TOOL, CAMPAIGN_LEAD_STATISTICS_TOOL, CAMPAIGN_MAILBOX_STATISTICS_TOOL, DOWNLOAD_CAMPAIGN_DATA_TOOL, VIEW_DOWNLOAD_STATISTICS_TOOL, ]; ``` -------------------------------------------------------------------------------- /src/handlers/lead.ts: -------------------------------------------------------------------------------- ```typescript import { AxiosInstance } from 'axios'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { isListLeadsParams, isGetLeadParams, isAddLeadToCampaignParams, isUpdateLeadParams, isUpdateLeadStatusParams, isBulkImportLeadsParams, isDeleteLeadParams } from '../types/lead.js'; // Handler for lead-related tools export async function handleLeadTool( toolName: string, args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { switch (toolName) { case 'smartlead_list_leads': { return handleListLeads(args, apiClient, withRetry); } case 'smartlead_get_lead': { return handleGetLead(args, apiClient, withRetry); } case 'smartlead_add_lead_to_campaign': { return handleAddLeadToCampaign(args, apiClient, withRetry); } case 'smartlead_update_lead': { return handleUpdateLead(args, apiClient, withRetry); } case 'smartlead_update_lead_status': { return handleUpdateLeadStatus(args, apiClient, withRetry); } case 'smartlead_bulk_import_leads': { return handleBulkImportLeads(args, apiClient, withRetry); } case 'smartlead_delete_lead': { return handleDeleteLead(args, apiClient, withRetry); } default: throw new Error(`Unknown lead tool: ${toolName}`); } } // Individual handlers for each tool async function handleListLeads( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isListLeadsParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_list_leads' ); } try { // Build query parameters from args const params = new URLSearchParams(); if (args.campaign_id !== undefined) { params.append('campaign_id', args.campaign_id.toString()); } if (args.status !== undefined) { params.append('status', args.status); } if (args.limit !== undefined) { params.append('limit', args.limit.toString()); } if (args.offset !== undefined) { params.append('offset', args.offset.toString()); } if (args.search !== undefined) { params.append('search', args.search); } if (args.start_date !== undefined) { params.append('start_date', args.start_date); } if (args.end_date !== undefined) { params.append('end_date', args.end_date); } const response = await withRetry( async () => apiClient.get('/leads', { params }), 'list leads' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleGetLead( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isGetLeadParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_get_lead' ); } try { const response = await withRetry( async () => apiClient.get(`/leads/${args.lead_id}`), 'get lead' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleAddLeadToCampaign( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isAddLeadToCampaignParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_add_lead_to_campaign' ); } try { const response = await withRetry( async () => apiClient.post(`/campaigns/${args.campaign_id}/leads`, args), 'add lead to campaign' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleUpdateLead( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isUpdateLeadParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_update_lead' ); } const { lead_id, ...leadData } = args; try { const response = await withRetry( async () => apiClient.put(`/leads/${lead_id}`, leadData), 'update lead' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleUpdateLeadStatus( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isUpdateLeadStatusParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_update_lead_status' ); } const { lead_id, status } = args; try { const response = await withRetry( async () => apiClient.put(`/leads/${lead_id}/status`, { status }), 'update lead status' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleBulkImportLeads( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isBulkImportLeadsParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_bulk_import_leads' ); } try { const response = await withRetry( async () => apiClient.post(`/campaigns/${args.campaign_id}/leads/bulk`, { leads: args.leads }), 'bulk import leads' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } async function handleDeleteLead( args: unknown, apiClient: AxiosInstance, withRetry: <T>(operation: () => Promise<T>, context: string) => Promise<T> ) { if (!isDeleteLeadParams(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for smartlead_delete_lead' ); } try { const response = await withRetry( async () => apiClient.delete(`/leads/${args.lead_id}`), 'delete lead' ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], isError: false, }; } catch (error: any) { return { content: [{ type: 'text', text: `API Error: ${error.response?.data?.message || error.message}` }], isError: true, }; } } ``` -------------------------------------------------------------------------------- /src/licensing/index.ts: -------------------------------------------------------------------------------- ```typescript import axios from 'axios'; import * as dotenv from 'dotenv'; // Ensure .env file is loaded dotenv.config(); // License levels export enum LicenseLevel { FREE = 'free', BASIC = 'basic', PREMIUM = 'premium' } // Feature definitions export interface LicenseFeatures { allowedCategories: string[]; maxRequests: number; n8nIntegration: boolean; smartleadApiAccess: boolean; } // Licensing configuration - This is just a fallback when offline const LICENSE_CONFIG: Record<LicenseLevel, LicenseFeatures> = { [LicenseLevel.FREE]: { allowedCategories: ['campaignManagement', 'leadManagement'], maxRequests: 100, n8nIntegration: false, smartleadApiAccess: true }, [LicenseLevel.BASIC]: { allowedCategories: ['campaignManagement', 'leadManagement', 'campaignStatistics', 'smartDelivery', 'webhooks', 'clientManagement', 'smartSenders'], maxRequests: 1000, n8nIntegration: true, smartleadApiAccess: true }, [LicenseLevel.PREMIUM]: { allowedCategories: ['campaignManagement', 'leadManagement', 'campaignStatistics', 'smartDelivery', 'webhooks', 'clientManagement', 'smartSenders'], maxRequests: 10000, n8nIntegration: true, smartleadApiAccess: true } }; // License validation result export interface LicenseValidationResult { valid: boolean; level: LicenseLevel; features: LicenseFeatures; message: string; usageCount: number; } // Generation info for server-side validation export interface FeatureRequestToken { token: string; expires: number; } // Validation status cache let cachedValidation: LicenseValidationResult | null = null; let cachedFeatureToken: FeatureRequestToken | null = null; const requestCounts: Record<string, number> = {}; // API configuration // Use the environment variable for the license server URL const LICENSE_SERVER_URL = process.env.LICENSE_SERVER_URL; const LICENSING_CACHE_TTL = 3600000; // 1 hour let lastValidationTime = 0; /** * Validate a license key * @param licenseKey Optional: license key to validate (primarily uses env var) * @returns License validation result */ export async function validateLicense(): Promise<LicenseValidationResult> { // Always return PREMIUM license regardless of key console.log('✅ License override: All features enabled in PREMIUM mode'); return createValidationResult( LicenseLevel.PREMIUM, true, 'License override enabled: All features available', 0 ); // The following code will never be reached due to the early return above // Original code remains for reference but won't be executed: // Use the license key from the specific env var first // const apiKey = process.env.JEAN_LICENSE_KEY; // ... existing code ... } /** * Get a token for server-side feature validation * This adds security since critical operations will need server validation */ export async function getFeatureToken(): Promise<FeatureRequestToken | null> { // Ensure we have a valid license first const licenseResult = await validateLicense(); // Check if we have a valid cached token if (cachedFeatureToken && Date.now() < cachedFeatureToken.expires) { return cachedFeatureToken; } // If no license server URL or not in BASIC or PREMIUM tier, don't try to get a token if (!LICENSE_SERVER_URL || (licenseResult.level !== LicenseLevel.BASIC && licenseResult.level !== LicenseLevel.PREMIUM)) { return null; } // Try to get a fresh token from the server try { const apiKey = process.env.JEAN_LICENSE_KEY; if (!apiKey) return null; console.log(`Requesting feature token from ${LICENSE_SERVER_URL}/token`); const response = await axios.post(`${LICENSE_SERVER_URL}/token`, {}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, timeout: 5000 }); if (response.data && response.data.token) { cachedFeatureToken = { token: response.data.token, expires: response.data.expires || (Date.now() + 3600000) // Default to 1 hour if not specified }; console.log(`✅ Feature token acquired, valid until ${new Date(cachedFeatureToken.expires).toISOString()}`); return cachedFeatureToken; } return null; } catch (error: any) { console.error(`Failed to get feature token: ${error.message}`); // Silent failure - just return null return null; } } /** * Track usage for a license * @param licenseKey The license key * @param toolName The tool being used */ export async function trackUsage(licenseKey?: string, toolName = 'unknown'): Promise<void> { const apiKey = licenseKey || process.env.JEAN_LICENSE_KEY; if (!apiKey || !LICENSE_SERVER_URL) return; // Get a unique identifier for tracking const machineId = getMachineId(); // Increment local counter const key = apiKey.substring(0, 8); // Use part of the key as an identifier requestCounts[key] = (requestCounts[key] || 0) + 1; // Only report every 10 requests to reduce API load if (requestCounts[key] % 10 !== 0) return; try { // Report usage asynchronously (don't await) axios.post(`${LICENSE_SERVER_URL}/track`, { key: apiKey, tool: toolName, count: 10, // Batch reporting machineId }, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } }).catch((error) => { // Silently fail with more context - we don't want to impact performance console.debug(`Usage tracking failed: ${error.message}`); }); } catch (error) { // Ignore errors - usage tracking is non-critical } } /** * Create a validation result object */ function createValidationResult( level: LicenseLevel, valid: boolean, message: string, usageCount = 0 ): LicenseValidationResult { return { valid, level, features: LICENSE_CONFIG[level], message, usageCount }; } /** * Check if a feature is available in the current license * @param feature The feature to check * @returns Whether the feature is available */ export async function isFeatureEnabled(feature: keyof LicenseFeatures): Promise<boolean> { // Always return true for all features return true; } /** * Check if a category is available in the current license * @param category The category to check * @returns Whether the category is available */ export async function isCategoryEnabled(category: string): Promise<boolean> { // Always return true for all categories return true; } /** * Get the current license information * @returns Current license information */ export async function getLicenseInfo(): Promise<LicenseValidationResult> { return validateLicense(); } /** * Get a unique identifier for the machine * This helps prevent sharing of license keys */ function getMachineId(): string { try { // Use environment variables to create a semi-unique ID // This is not perfect but provides basic machine identification const os = process.platform; const cpus = process.env.NUMBER_OF_PROCESSORS || ''; const username = process.env.USER || process.env.USERNAME || ''; const hostname = process.env.HOSTNAME || ''; // Create a simple hash of these values const combinedString = `${os}-${cpus}-${username}-${hostname}`; let hash = 0; for (let i = 0; i < combinedString.length; i++) { hash = ((hash << 5) - hash) + combinedString.charCodeAt(i); hash |= 0; // Convert to 32bit integer } return Math.abs(hash).toString(16); } catch (e) { // Fallback to a random ID if we can't get system info return Math.random().toString(36).substring(2, 15); } } /** * Print a summary of the current license status to the console * This is useful for displaying on server startup */ export async function printLicenseStatus(): Promise<void> { try { const validation = await validateLicense(); console.log('\n========== LICENSE STATUS =========='); console.log(`License Tier: ${validation.level.toUpperCase()}`); console.log(`Valid: ${validation.valid ? 'Yes' : 'No'}`); console.log(`Message: ${validation.message}`); console.log('Available Features:'); console.log(`- Categories: ${validation.features.allowedCategories.join(', ')}`); console.log(`- Max Requests: ${validation.features.maxRequests}`); console.log(`- n8n Integration: ${validation.features.n8nIntegration ? 'Enabled' : 'Disabled'}`); console.log(`- Smartlead API Access: ${validation.features.smartleadApiAccess ? 'Enabled' : 'Disabled'}`); if (!validation.valid) { console.log('\n⚠️ ATTENTION ⚠️'); console.log('Your license is not valid or is running in limited mode.'); if (!process.env.JEAN_LICENSE_KEY) { console.log('You have not set a license key (JEAN_LICENSE_KEY) in your .env file.'); } if (!process.env.LICENSE_SERVER_URL) { console.log('The LICENSE_SERVER_URL is not configured in your .env file.'); } console.log('To enable all features, please check your configuration.'); } if (process.env.LICENSE_LEVEL_OVERRIDE) { console.log('\n⚠️ DEVELOPMENT MODE ⚠️'); console.log(`License level is overridden to: ${process.env.LICENSE_LEVEL_OVERRIDE.toUpperCase()}`); console.log('This override should not be used in production environments.'); } console.log('=====================================\n'); } catch (error) { console.error('Failed to print license status:', error); } } ``` -------------------------------------------------------------------------------- /server/license-server.js: -------------------------------------------------------------------------------- ```javascript import express from 'express'; import crypto from 'crypto'; import { createClient } from '@supabase/supabase-js'; import Stripe from 'stripe'; import dotenv from 'dotenv'; dotenv.config(); // Initialize Express app const app = express(); app.use(express.json()); // Initialize Stripe const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); // Initialize Supabase client const supabase = createClient( process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY ); // License levels const LicenseLevel = { FREE: 'free', BASIC: 'basic', PREMIUM: 'premium' }; // Feature mappings const LicenseFeatures = { [LicenseLevel.FREE]: { allowedCategories: ['campaignManagement', 'leadManagement'], maxRequests: 100, n8nIntegration: false, smartleadApiAccess: true }, [LicenseLevel.BASIC]: { allowedCategories: ['campaignManagement', 'leadManagement', 'campaignStatistics', 'smartDelivery'], maxRequests: 1000, n8nIntegration: false, smartleadApiAccess: true }, [LicenseLevel.PREMIUM]: { allowedCategories: ['campaignManagement', 'leadManagement', 'campaignStatistics', 'smartDelivery', 'webhooks', 'clientManagement', 'smartSenders'], maxRequests: 10000, n8nIntegration: true, smartleadApiAccess: true } }; // Generate a secure license key function generateLicenseKey() { return crypto.randomBytes(24).toString('hex'); } // Generate a temporary feature token function generateFeatureToken() { return { token: crypto.randomBytes(32).toString('hex'), expires: Date.now() + 3600000 // 1 hour }; } // API Routes // Validate a license key app.post('/validate', async (req, res) => { try { const { Authorization } = req.headers; const apiKey = Authorization ? Authorization.replace('Bearer ', '') : null; const machineId = req.headers['x-client-id']; if (!apiKey) { return res.json({ valid: false, level: LicenseLevel.FREE, message: 'No API key provided' }); } // Query license in Supabase const { data, error } = await supabase .from('licenses') .select('*') .eq('key', apiKey) .single(); if (error || !data) { return res.json({ valid: false, level: LicenseLevel.FREE, message: 'Invalid license key' }); } // Check if license is expired if (data.expires && new Date(data.expires) < new Date()) { return res.json({ valid: false, level: LicenseLevel.FREE, message: 'License expired' }); } // Check if this machine is authorized if (data.machine_ids && data.machine_ids.length > 0) { if (!machineId || !data.machine_ids.includes(machineId)) { // If this is a new machine, check if we've reached the limit if (data.machine_ids.length >= data.max_machines) { return res.json({ valid: false, level: LicenseLevel.FREE, message: 'Maximum number of machines reached' }); } // Otherwise add this machine to the authorized list const updatedMachines = [...data.machine_ids, machineId]; await supabase .from('licenses') .update({ machine_ids: updatedMachines }) .eq('id', data.id); } } else { // Initialize the machine_ids array with this machine await supabase .from('licenses') .update({ machine_ids: [machineId] }) .eq('id', data.id); } // Generate a feature token for server-side validation const featureToken = generateFeatureToken(); // Track usage await supabase .from('license_usage') .insert({ license_id: data.id, machine_id: machineId, timestamp: new Date().toISOString() }); // Increment usage count const { count } = await supabase .from('license_usage') .select('count', { count: 'exact' }) .eq('license_id', data.id); return res.json({ valid: true, level: data.level, usage: count || 0, featureToken: featureToken.token, tokenExpires: 3600, // 1 hour in seconds message: 'License validated successfully' }); } catch (error) { console.error('License validation error:', error); return res.status(500).json({ valid: false, level: LicenseLevel.FREE, message: 'Server error during validation' }); } }); // Validate an installation app.post('/validate-install', async (req, res) => { try { const { apiKey, machineId, version, installPath } = req.body; if (!apiKey) { return res.json({ success: false, message: 'No API key provided' }); } // Query license in Supabase const { data, error } = await supabase .from('licenses') .select('*') .eq('key', apiKey) .single(); if (error || !data) { return res.json({ success: false, message: 'Invalid license key' }); } // Check if license is expired if (data.expires && new Date(data.expires) < new Date()) { return res.json({ success: false, message: 'License expired' }); } // Record installation info await supabase .from('installations') .insert({ license_id: data.id, machine_id: machineId, version, install_path: installPath, installed_at: new Date().toISOString() }); // Return license details return res.json({ success: true, level: data.level, features: LicenseFeatures[data.level].allowedCategories, expires: data.expires, message: 'Installation validated successfully' }); } catch (error) { console.error('Installation validation error:', error); return res.status(500).json({ success: false, message: 'Server error during installation validation' }); } }); // Generate a feature token (used for server-side validation) app.post('/token', async (req, res) => { try { const { Authorization } = req.headers; const apiKey = Authorization ? Authorization.replace('Bearer ', '') : null; if (!apiKey) { return res.status(401).json({ error: 'Unauthorized' }); } // Verify the license is valid const { data, error } = await supabase .from('licenses') .select('level') .eq('key', apiKey) .single(); if (error || !data) { return res.status(401).json({ error: 'Invalid license' }); } // Generate a new token const featureToken = generateFeatureToken(); // Store the token in Supabase with expiration await supabase .from('feature_tokens') .insert({ token: featureToken.token, license_key: apiKey, expires_at: new Date(featureToken.expires).toISOString() }); return res.json({ token: featureToken.token, expires: featureToken.expires }); } catch (error) { console.error('Token generation error:', error); return res.status(500).json({ error: 'Server error' }); } }); // Handle Stripe webhook app.post('/webhook', async (req, res) => { const sig = req.headers['stripe-signature']; try { const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET ); switch (event.type) { case 'customer.subscription.created': case 'customer.subscription.updated': { const subscription = event.data.object; const customerId = subscription.customer; // Get the product details to determine license level const product = await stripe.products.retrieve( subscription.items.data[0].plan.product ); // Get license level from product metadata const level = product.metadata.license_level || LicenseLevel.BASIC; // Generate a license key const licenseKey = generateLicenseKey(); // Get the customer email const customer = await stripe.customers.retrieve(customerId); // Store the license in Supabase await supabase .from('licenses') .upsert({ key: licenseKey, customer_id: customerId, customer_email: customer.email, level, created_at: new Date().toISOString(), expires: subscription.current_period_end ? new Date(subscription.current_period_end * 1000).toISOString() : null, max_machines: level === LicenseLevel.PREMIUM ? 5 : 2, // Limit based on tier machine_ids: [], active: true }, { onConflict: 'customer_id' }); // Update customer metadata with license key await stripe.customers.update(customerId, { metadata: { license_key: licenseKey, license_level: level } }); break; } case 'customer.subscription.deleted': { const subscription = event.data.object; const customerId = subscription.customer; // Find the license by customer ID const { data } = await supabase .from('licenses') .select('id') .eq('customer_id', customerId) .single(); if (data) { // Downgrade the license to free tier await supabase .from('licenses') .update({ level: LicenseLevel.FREE, expires: new Date().toISOString() // Expire now }) .eq('id', data.id); } break; } } res.json({ received: true }); } catch (error) { console.error('Webhook error:', error); res.status(400).json({ error: error.message }); } }); // Start server const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`License server running on port ${PORT}`); }); ``` -------------------------------------------------------------------------------- /src/tools/email.ts: -------------------------------------------------------------------------------- ```typescript import { CategoryTool, ToolCategory } from '../types/common.js'; // Email Account Management Tools export const LIST_EMAIL_ACCOUNTS_CAMPAIGN_TOOL: CategoryTool = { name: 'smartlead_list_email_accounts_campaign', description: 'List all email accounts associated with a specific campaign.', category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to get email accounts for', }, status: { type: 'string', enum: ['active', 'disconnected', 'pending'], description: 'Filter email accounts by status', }, limit: { type: 'number', description: 'Maximum number of email accounts to return', }, offset: { type: 'number', description: 'Offset for pagination', }, }, required: ['campaign_id'], }, }; export const ADD_EMAIL_TO_CAMPAIGN_TOOL: CategoryTool = { name: 'smartlead_add_email_to_campaign', description: 'Add an email account to a campaign.', category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to add the email account to', }, email_account_id: { type: 'number', description: 'ID of the email account to add to the campaign', }, }, required: ['campaign_id', 'email_account_id'], }, }; export const REMOVE_EMAIL_FROM_CAMPAIGN_TOOL: CategoryTool = { name: 'smartlead_remove_email_from_campaign', description: 'Remove an email account from a campaign.', category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to remove the email account from', }, email_account_id: { type: 'number', description: 'ID of the email account to remove from the campaign', }, }, required: ['campaign_id', 'email_account_id'], }, }; export const FETCH_EMAIL_ACCOUNTS_TOOL: CategoryTool = { name: 'smartlead_fetch_email_accounts', description: 'Fetch all email accounts associated with the user.', category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['active', 'disconnected', 'pending'], description: 'Filter email accounts by status', }, limit: { type: 'number', description: 'Maximum number of email accounts to return', }, offset: { type: 'number', description: 'Offset for pagination', }, }, }, }; export const CREATE_EMAIL_ACCOUNT_TOOL: CategoryTool = { name: 'smartlead_create_email_account', description: 'Create a new email account.', category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, inputSchema: { type: 'object', properties: { email: { type: 'string', description: 'Email address', }, provider: { type: 'string', description: 'Email provider (e.g., "gmail", "outlook", "custom")', }, name: { type: 'string', description: 'Display name for the email account', }, smtp_host: { type: 'string', description: 'SMTP server hostname (for custom providers)', }, smtp_port: { type: 'number', description: 'SMTP server port (for custom providers)', }, smtp_username: { type: 'string', description: 'SMTP username (for custom providers)', }, smtp_password: { type: 'string', description: 'SMTP password (for custom providers)', }, imap_host: { type: 'string', description: 'IMAP server hostname (for custom providers)', }, imap_port: { type: 'number', description: 'IMAP server port (for custom providers)', }, imap_username: { type: 'string', description: 'IMAP username (for custom providers)', }, imap_password: { type: 'string', description: 'IMAP password (for custom providers)', }, oauth_token: { type: 'string', description: 'OAuth token (for OAuth-based providers)', }, tags: { type: 'array', items: { type: 'string', }, description: 'Tags to assign to the email account', }, }, required: ['email', 'provider'], }, }; export const UPDATE_EMAIL_ACCOUNT_TOOL: CategoryTool = { name: 'smartlead_update_email_account', description: 'Update an existing email account.', category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, inputSchema: { type: 'object', properties: { email_account_id: { type: 'number', description: 'ID of the email account to update', }, name: { type: 'string', description: 'Display name for the email account', }, smtp_host: { type: 'string', description: 'SMTP server hostname', }, smtp_port: { type: 'number', description: 'SMTP server port', }, smtp_username: { type: 'string', description: 'SMTP username', }, smtp_password: { type: 'string', description: 'SMTP password', }, imap_host: { type: 'string', description: 'IMAP server hostname', }, imap_port: { type: 'number', description: 'IMAP server port', }, imap_username: { type: 'string', description: 'IMAP username', }, imap_password: { type: 'string', description: 'IMAP password', }, oauth_token: { type: 'string', description: 'OAuth token', }, status: { type: 'string', enum: ['active', 'paused', 'disconnected'], description: 'Status of the email account', }, }, required: ['email_account_id'], }, }; export const FETCH_EMAIL_ACCOUNT_BY_ID_TOOL: CategoryTool = { name: 'smartlead_fetch_email_account_by_id', description: 'Fetch a specific email account by ID.', category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, inputSchema: { type: 'object', properties: { email_account_id: { type: 'number', description: 'ID of the email account to fetch', }, }, required: ['email_account_id'], }, }; export const UPDATE_EMAIL_WARMUP_TOOL: CategoryTool = { name: 'smartlead_update_email_warmup', description: 'Add or update warmup settings for an email account.', category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, inputSchema: { type: 'object', properties: { email_account_id: { type: 'number', description: 'ID of the email account to update warmup settings for', }, enabled: { type: 'boolean', description: 'Whether warmup is enabled for this email account', }, daily_limit: { type: 'number', description: 'Daily limit for warmup emails', }, warmup_settings: { type: 'object', properties: { start_time: { type: 'string', description: 'Start time for warmup in HH:MM format', }, end_time: { type: 'string', description: 'End time for warmup in HH:MM format', }, days_of_week: { type: 'array', items: { type: 'number', }, description: 'Days of the week for warmup (1-7, where 1 is Monday)', }, }, description: 'Additional warmup settings', }, }, required: ['email_account_id', 'enabled'], }, }; export const RECONNECT_EMAIL_ACCOUNT_TOOL: CategoryTool = { name: 'smartlead_reconnect_email_account', description: 'Reconnect a failed email account.', category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, inputSchema: { type: 'object', properties: { email_account_id: { type: 'number', description: 'ID of the email account to reconnect', }, connection_details: { type: 'object', properties: { smtp_host: { type: 'string', description: 'SMTP server hostname', }, smtp_port: { type: 'number', description: 'SMTP server port', }, smtp_username: { type: 'string', description: 'SMTP username', }, smtp_password: { type: 'string', description: 'SMTP password', }, imap_host: { type: 'string', description: 'IMAP server hostname', }, imap_port: { type: 'number', description: 'IMAP server port', }, imap_username: { type: 'string', description: 'IMAP username', }, imap_password: { type: 'string', description: 'IMAP password', }, oauth_token: { type: 'string', description: 'OAuth token', }, }, description: 'Connection details for reconnecting the email account', }, }, required: ['email_account_id'], }, }; export const UPDATE_EMAIL_ACCOUNT_TAG_TOOL: CategoryTool = { name: 'smartlead_update_email_account_tag', description: 'Update tags for an email account.', category: ToolCategory.EMAIL_ACCOUNT_MANAGEMENT, inputSchema: { type: 'object', properties: { email_account_id: { type: 'number', description: 'ID of the email account to update tags for', }, tags: { type: 'array', items: { type: 'string', }, description: 'Tags to assign to the email account', }, }, required: ['email_account_id', 'tags'], }, }; // Export all email tools as an array export const emailTools = [ LIST_EMAIL_ACCOUNTS_CAMPAIGN_TOOL, ADD_EMAIL_TO_CAMPAIGN_TOOL, REMOVE_EMAIL_FROM_CAMPAIGN_TOOL, FETCH_EMAIL_ACCOUNTS_TOOL, CREATE_EMAIL_ACCOUNT_TOOL, UPDATE_EMAIL_ACCOUNT_TOOL, FETCH_EMAIL_ACCOUNT_BY_ID_TOOL, UPDATE_EMAIL_WARMUP_TOOL, RECONNECT_EMAIL_ACCOUNT_TOOL, UPDATE_EMAIL_ACCOUNT_TAG_TOOL, ]; ``` -------------------------------------------------------------------------------- /src/tools/campaign.ts: -------------------------------------------------------------------------------- ```typescript import { CategoryTool, ToolCategory } from '../types/common.js'; // Campaign Management Tools export const CREATE_CAMPAIGN_TOOL: CategoryTool = { name: 'smartlead_create_campaign', description: 'Create a new campaign in Smartlead.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the campaign', }, client_id: { type: 'number', description: 'Client ID for the campaign', }, }, required: ['name'], }, }; export const UPDATE_CAMPAIGN_SCHEDULE_TOOL: CategoryTool = { name: 'smartlead_update_campaign_schedule', description: 'Update a campaign\'s schedule settings.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to update', }, timezone: { type: 'string', description: 'Timezone for the campaign (e.g., "America/Los_Angeles")', }, days_of_the_week: { type: 'array', items: { type: 'number' }, description: 'Days of the week to send emails (1-7, where 1 is Monday)', }, start_hour: { type: 'string', description: 'Start hour in 24-hour format (e.g., "09:00")', }, end_hour: { type: 'string', description: 'End hour in 24-hour format (e.g., "17:00")', }, min_time_btw_emails: { type: 'number', description: 'Minimum time between emails in minutes', }, max_new_leads_per_day: { type: 'number', description: 'Maximum number of new leads per day', }, schedule_start_time: { type: 'string', description: 'Schedule start time in ISO format', }, }, required: ['campaign_id'], }, }; export const UPDATE_CAMPAIGN_SETTINGS_TOOL: CategoryTool = { name: 'smartlead_update_campaign_settings', description: 'Update a campaign\'s general settings.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to update', }, name: { type: 'string', description: 'New name for the campaign', }, status: { type: 'string', enum: ['active', 'paused', 'completed'], description: 'Status of the campaign', }, settings: { type: 'object', description: 'Additional campaign settings', }, }, required: ['campaign_id'], }, }; export const UPDATE_CAMPAIGN_STATUS_TOOL: CategoryTool = { name: 'smartlead_update_campaign_status', description: 'Update the status of a campaign. Use this specifically for changing a campaign\'s status.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to update the status for', }, status: { type: 'string', enum: ['PAUSED', 'STOPPED', 'START'], description: 'New status for the campaign (must be in uppercase)', }, }, required: ['campaign_id', 'status'], }, }; export const GET_CAMPAIGN_TOOL: CategoryTool = { name: 'smartlead_get_campaign', description: 'Get details of a specific campaign by ID.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to retrieve', }, }, required: ['campaign_id'], }, }; export const LIST_CAMPAIGNS_TOOL: CategoryTool = { name: 'smartlead_list_campaigns', description: 'List all campaigns with optional filtering.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['active', 'paused', 'completed'], description: 'Filter campaigns by status', }, limit: { type: 'number', description: 'Maximum number of campaigns to return', }, offset: { type: 'number', description: 'Offset for pagination', }, }, }, }; export const SAVE_CAMPAIGN_SEQUENCE_TOOL: CategoryTool = { name: 'smartlead_save_campaign_sequence', description: 'Save a sequence of emails for a campaign.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign', }, sequence: { type: 'array', items: { type: 'object', properties: { seq_number: { type: 'number', description: 'The sequence number (order) of this email', }, seq_delay_details: { type: 'object', properties: { delay_in_days: { type: 'number', description: 'Days to wait before sending this email', } }, description: 'Delay details for this sequence' }, variant_distribution_type: { type: 'string', enum: ['MANUAL_EQUAL', 'MANUAL_PERCENTAGE', 'AI_EQUAL'], description: 'How to distribute variants' }, lead_distribution_percentage: { type: 'number', description: 'What sample % size of the lead pool to use to find the winner (for AI_EQUAL)' }, winning_metric_property: { type: 'string', enum: ['OPEN_RATE', 'CLICK_RATE', 'REPLY_RATE', 'POSITIVE_REPLY_RATE'], description: 'Metric to use for determining the winning variant (for AI_EQUAL)' }, seq_variants: { type: 'array', items: { type: 'object', properties: { subject: { type: 'string', description: 'Email subject line', }, email_body: { type: 'string', description: 'Email body content in HTML', }, variant_label: { type: 'string', description: 'Label for this variant (A, B, C, etc.)', }, variant_distribution_percentage: { type: 'number', description: 'Percentage of leads to receive this variant (for MANUAL_PERCENTAGE)' } }, required: ['subject', 'email_body', 'variant_label'], }, description: 'Variants of the email in this sequence' } }, required: ['seq_number', 'seq_delay_details', 'variant_distribution_type', 'seq_variants'], }, description: 'Sequence of emails to send', }, }, required: ['campaign_id', 'sequence'], }, }; export const GET_CAMPAIGN_SEQUENCE_TOOL: CategoryTool = { name: 'smartlead_get_campaign_sequence', description: 'Fetch a campaign\'s sequence data.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to fetch sequences for', }, }, required: ['campaign_id'], }, }; // New tool definitions for the remaining campaign management API endpoints export const GET_CAMPAIGNS_BY_LEAD_TOOL: CategoryTool = { name: 'smartlead_get_campaigns_by_lead', description: 'Fetch all campaigns that a lead belongs to.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { lead_id: { type: 'number', description: 'ID of the lead to fetch campaigns for', }, }, required: ['lead_id'], }, }; export const EXPORT_CAMPAIGN_LEADS_TOOL: CategoryTool = { name: 'smartlead_export_campaign_leads', description: 'Export all leads data from a campaign as CSV.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to export leads from', }, }, required: ['campaign_id'], }, }; export const DELETE_CAMPAIGN_TOOL: CategoryTool = { name: 'smartlead_delete_campaign', description: 'Delete a campaign permanently.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to delete', }, }, required: ['campaign_id'], }, }; export const GET_CAMPAIGN_ANALYTICS_BY_DATE_TOOL: CategoryTool = { name: 'smartlead_get_campaign_analytics_by_date', description: 'Fetch campaign analytics for a specific date range.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to fetch analytics for', }, start_date: { type: 'string', format: 'date', description: 'Start date in YYYY-MM-DD format', }, end_date: { type: 'string', format: 'date', description: 'End date in YYYY-MM-DD format', }, }, required: ['campaign_id', 'start_date', 'end_date'], }, }; export const GET_CAMPAIGN_SEQUENCE_ANALYTICS_TOOL: CategoryTool = { name: 'smartlead_get_campaign_sequence_analytics', description: 'Fetch analytics data for a specific email campaign sequence.', category: ToolCategory.CAMPAIGN_MANAGEMENT, inputSchema: { type: 'object', properties: { campaign_id: { type: 'number', description: 'ID of the campaign to fetch sequence analytics for', }, start_date: { type: 'string', description: 'Start date in YYYY-MM-DD HH:MM:SS format', }, end_date: { type: 'string', description: 'End date in YYYY-MM-DD HH:MM:SS format', }, time_zone: { type: 'string', description: 'Timezone for the analytics data (e.g., "Europe/London")', }, }, required: ['campaign_id', 'start_date', 'end_date'], }, }; // Export an array of all campaign tools for registration export const campaignTools = [ CREATE_CAMPAIGN_TOOL, UPDATE_CAMPAIGN_SCHEDULE_TOOL, UPDATE_CAMPAIGN_SETTINGS_TOOL, UPDATE_CAMPAIGN_STATUS_TOOL, GET_CAMPAIGN_TOOL, LIST_CAMPAIGNS_TOOL, SAVE_CAMPAIGN_SEQUENCE_TOOL, GET_CAMPAIGN_SEQUENCE_TOOL, GET_CAMPAIGNS_BY_LEAD_TOOL, EXPORT_CAMPAIGN_LEADS_TOOL, DELETE_CAMPAIGN_TOOL, GET_CAMPAIGN_ANALYTICS_BY_DATE_TOOL, GET_CAMPAIGN_SEQUENCE_ANALYTICS_TOOL ]; ``` -------------------------------------------------------------------------------- /src/types/smartDelivery.ts: -------------------------------------------------------------------------------- ```typescript // Type definitions for SmartDelivery functionality // Region wise Provider IDs response types export interface SpamTestProvider { id: number; name: string; description?: string; region?: string; country?: string; is_active: boolean; } export interface RegionWiseProvidersResponse { success: boolean; data: { providers: SpamTestProvider[]; }; message?: string; } // Manual Placement Test types export interface CreateManualPlacementTestParams { test_name: string; description?: string; spam_filters: string[]; link_checker: boolean; campaign_id: number; sequence_mapping_id: number; provider_ids: number[]; sender_accounts: string[]; all_email_sent_without_time_gap: boolean; min_time_btwn_emails: number; min_time_unit: string; is_warmup: boolean; } // Automated Placement Test additional types export interface CreateAutomatedPlacementTestParams extends CreateManualPlacementTestParams { schedule_start_time: string; test_end_date: string; every_days: number; tz: string; days: number[]; starHour?: string; folder_id?: number; scheduler_cron_value?: { tz: string; days: number[]; }; } // Spam Test Details types export interface GetSpamTestDetailsParams { spam_test_id: number; } // Delete Smart Delivery Tests types export interface DeleteSmartDeliveryTestsParams { spamTestIds: number[]; } // Stop Automated Test types export interface StopAutomatedTestParams { spam_test_id: number; } // List all Tests types export interface ListAllTestsParams { testType: 'manual' | 'auto'; limit?: number; offset?: number; } // Provider wise report types export interface ProviderWiseReportParams { spam_test_id: number; } // Geo wise report types export interface GroupWiseReportParams { spam_test_id: number; } // Sender Account wise report types export interface SenderAccountWiseReportParams { spam_test_id: number; } // Spam filter report types export interface SpamFilterDetailsParams { spam_test_id: number; } // DKIM details types export interface DkimDetailsParams { spam_test_id: number; } // SPF details types export interface SpfDetailsParams { spam_test_id: number; } // rDNS report types export interface RdnsDetailsParams { spam_test_id: number; } // Sender Account list types export interface SenderAccountsParams { spam_test_id: number; } // Blacklists types export interface BlacklistParams { spam_test_id: number; } // Spam test email content types export interface EmailContentParams { spam_test_id: number; } // Spam test IP blacklist count types export interface IpAnalyticsParams { spam_test_id: number; } // Email reply headers types export interface EmailHeadersParams { spam_test_id: number; reply_id: number; } // Schedule history for automated tests types export interface ScheduleHistoryParams { spam_test_id: number; } // IP details types export interface IpDetailsParams { spam_test_id: number; reply_id: number; } // Mailbox summary types export interface MailboxSummaryParams { limit?: number; offset?: number; } // Mailbox count API types export interface MailboxCountParams { // This endpoint doesn't require any specific parameters } // Get all folders types export interface GetAllFoldersParams { limit?: number; offset?: number; name?: string; } // Create folders types export interface CreateFolderParams { name: string; } // Get folder by ID types export interface GetFolderByIdParams { folder_id: number; } // Delete folder types export interface DeleteFolderParams { folder_id: number; } // Tool parameter interfaces export interface GetRegionWiseProvidersParams { // This endpoint doesn't require any specific parameters beyond the API key // which is handled at the API client level } // Type guards export function isGetRegionWiseProvidersParams(args: unknown): args is GetRegionWiseProvidersParams { // Since this tool doesn't require specific parameters, any object is valid return typeof args === 'object' && args !== null; } export function isCreateManualPlacementTestParams(args: unknown): args is CreateManualPlacementTestParams { if (typeof args !== 'object' || args === null) return false; const params = args as Partial<CreateManualPlacementTestParams>; return ( typeof params.test_name === 'string' && Array.isArray(params.spam_filters) && typeof params.link_checker === 'boolean' && typeof params.campaign_id === 'number' && typeof params.sequence_mapping_id === 'number' && Array.isArray(params.provider_ids) && Array.isArray(params.sender_accounts) && typeof params.all_email_sent_without_time_gap === 'boolean' && typeof params.min_time_btwn_emails === 'number' && typeof params.min_time_unit === 'string' && typeof params.is_warmup === 'boolean' ); } export function isCreateAutomatedPlacementTestParams(args: unknown): args is CreateAutomatedPlacementTestParams { if (!isCreateManualPlacementTestParams(args)) return false; const params = args as Partial<CreateAutomatedPlacementTestParams>; return ( typeof params.schedule_start_time === 'string' && typeof params.test_end_date === 'string' && typeof params.every_days === 'number' && typeof params.tz === 'string' && Array.isArray(params.days) ); } export function isGetSpamTestDetailsParams(args: unknown): args is GetSpamTestDetailsParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as GetSpamTestDetailsParams).spam_test_id === 'number' ); } export function isDeleteSmartDeliveryTestsParams(args: unknown): args is DeleteSmartDeliveryTestsParams { return ( typeof args === 'object' && args !== null && 'spamTestIds' in args && Array.isArray((args as DeleteSmartDeliveryTestsParams).spamTestIds) && (args as DeleteSmartDeliveryTestsParams).spamTestIds.every(id => typeof id === 'number') ); } export function isStopAutomatedTestParams(args: unknown): args is StopAutomatedTestParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as StopAutomatedTestParams).spam_test_id === 'number' ); } export function isListAllTestsParams(args: unknown): args is ListAllTestsParams { if (typeof args !== 'object' || args === null) return false; const params = args as Partial<ListAllTestsParams>; return ( params.testType === 'manual' || params.testType === 'auto' ); } export function isProviderWiseReportParams(args: unknown): args is ProviderWiseReportParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as ProviderWiseReportParams).spam_test_id === 'number' ); } export function isGroupWiseReportParams(args: unknown): args is GroupWiseReportParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as GroupWiseReportParams).spam_test_id === 'number' ); } export function isSenderAccountWiseReportParams(args: unknown): args is SenderAccountWiseReportParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as SenderAccountWiseReportParams).spam_test_id === 'number' ); } export function isSpamFilterDetailsParams(args: unknown): args is SpamFilterDetailsParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as SpamFilterDetailsParams).spam_test_id === 'number' ); } export function isDkimDetailsParams(args: unknown): args is DkimDetailsParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as DkimDetailsParams).spam_test_id === 'number' ); } export function isSpfDetailsParams(args: unknown): args is SpfDetailsParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as SpfDetailsParams).spam_test_id === 'number' ); } export function isRdnsDetailsParams(args: unknown): args is RdnsDetailsParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as RdnsDetailsParams).spam_test_id === 'number' ); } export function isSenderAccountsParams(args: unknown): args is SenderAccountsParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as SenderAccountsParams).spam_test_id === 'number' ); } export function isBlacklistParams(args: unknown): args is BlacklistParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as BlacklistParams).spam_test_id === 'number' ); } export function isEmailContentParams(args: unknown): args is EmailContentParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as EmailContentParams).spam_test_id === 'number' ); } export function isIpAnalyticsParams(args: unknown): args is IpAnalyticsParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as IpAnalyticsParams).spam_test_id === 'number' ); } export function isEmailHeadersParams(args: unknown): args is EmailHeadersParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as EmailHeadersParams).spam_test_id === 'number' && 'reply_id' in args && typeof (args as EmailHeadersParams).reply_id === 'number' ); } export function isScheduleHistoryParams(args: unknown): args is ScheduleHistoryParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as ScheduleHistoryParams).spam_test_id === 'number' ); } export function isIpDetailsParams(args: unknown): args is IpDetailsParams { return ( typeof args === 'object' && args !== null && 'spam_test_id' in args && typeof (args as IpDetailsParams).spam_test_id === 'number' && 'reply_id' in args && typeof (args as IpDetailsParams).reply_id === 'number' ); } export function isMailboxSummaryParams(args: unknown): args is MailboxSummaryParams { return typeof args === 'object' && args !== null; } export function isMailboxCountParams(args: unknown): args is MailboxCountParams { return typeof args === 'object' && args !== null; } export function isGetAllFoldersParams(args: unknown): args is GetAllFoldersParams { return typeof args === 'object' && args !== null; } export function isCreateFolderParams(args: unknown): args is CreateFolderParams { return ( typeof args === 'object' && args !== null && 'name' in args && typeof (args as CreateFolderParams).name === 'string' ); } export function isGetFolderByIdParams(args: unknown): args is GetFolderByIdParams { return ( typeof args === 'object' && args !== null && 'folder_id' in args && typeof (args as GetFolderByIdParams).folder_id === 'number' ); } export function isDeleteFolderParams(args: unknown): args is DeleteFolderParams { return ( typeof args === 'object' && args !== null && 'folder_id' in args && typeof (args as DeleteFolderParams).folder_id === 'number' ); } ```