email-smtp-imap-mcp 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example ADDED
@@ -0,0 +1,48 @@
1
+ # Email MCP Server Configuration
2
+
3
+ # ===== OPTION 1: Multiple Accounts (JSON) =====
4
+ # Configure multiple email accounts in JSON format
5
+ EMAIL_ACCOUNTS_JSON='{
6
+ "work": {
7
+ "smtp_host": "smtp.mail.me.com",
8
+ "smtp_port": 587,
9
+ "smtp_secure": false,
10
+ "smtp_user": "your-email@icloud.com",
11
+ "smtp_pass": "your-app-specific-password",
12
+ "imap_host": "imap.mail.me.com",
13
+ "imap_port": 993,
14
+ "imap_secure": true,
15
+ "default_from_name": "Your Name"
16
+ },
17
+ "personal": {
18
+ "smtp_host": "smtp.gmail.com",
19
+ "smtp_port": 587,
20
+ "smtp_secure": false,
21
+ "smtp_user": "your-email@gmail.com",
22
+ "smtp_pass": "your-app-specific-password",
23
+ "imap_host": "imap.gmail.com",
24
+ "imap_port": 993,
25
+ "imap_secure": true,
26
+ "default_from_name": "Your Name"
27
+ }
28
+ }'
29
+
30
+ # Default account to use if not specified in tool calls
31
+ DEFAULT_EMAIL_ACCOUNT="work"
32
+
33
+ # ===== OPTION 2: Single Account (Individual Variables) =====
34
+ # Use these if you only have one email account
35
+ # SMTP_HOST="smtp.mail.me.com"
36
+ # SMTP_PORT=587
37
+ # SMTP_SECURE=false
38
+ # SMTP_USER="your-email@icloud.com"
39
+ # SMTP_PASS="your-app-specific-password"
40
+ # IMAP_HOST="imap.mail.me.com"
41
+ # IMAP_PORT=993
42
+ # IMAP_SECURE=true
43
+ # DEFAULT_FROM_NAME="Your Name"
44
+
45
+ # ===== NOTES =====
46
+ # For Gmail: Use App-Specific Passwords (not your regular password)
47
+ # For iCloud: Use App-Specific Passwords
48
+ # For other providers: Check their SMTP/IMAP settings documentation
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # Email MCP Server
2
+
3
+ A clean, simple MCP server for email operations (SMTP + IMAP).
4
+
5
+ ## Features
6
+
7
+ - ✅ **Send emails** (HTML + attachments)
8
+ - ✅ **Search emails** (flexible filters)
9
+ - ✅ **Reply/Forward** (with threading)
10
+ - ✅ **Organize** (mark read, archive, flag)
11
+ - ✅ **List folders**
12
+
13
+ ## Quick Start
14
+
15
+ ### 1. Install
16
+
17
+ ```bash
18
+ npm install
19
+ npm run build
20
+ ```
21
+
22
+ ### 2. Configure
23
+
24
+ Create `.env`:
25
+
26
+ ```env
27
+ EMAIL_ACCOUNTS_JSON={"icloud":{"smtp":{"host":"smtp.mail.me.com","port":587,"user":"your@icloud.com","password":"your-app-password"},"imap":{"host":"imap.mail.me.com","port":993},"default_from_name":"Your Name","sender_emails":["your@icloud.com","alias@domain.com"]}}
28
+ DEFAULT_EMAIL_ACCOUNT=icloud
29
+ ```
30
+
31
+ ### 3. Add to MCP-Supported Software
32
+
33
+ #### Claude Desktop
34
+
35
+ Add to your `claude_desktop_config.json`:
36
+
37
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
38
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "email": {
44
+ "command": "node",
45
+ "args": ["/absolute/path/to/email-smtp-imap-mcp/build/index.js"],
46
+ "env": {
47
+ "EMAIL_ACCOUNTS_JSON": "{\"icloud\":{\"smtp\":{\"host\":\"smtp.mail.me.com\",\"port\":587,\"user\":\"your@icloud.com\",\"password\":\"your-app-password\"},\"imap\":{\"host\":\"imap.mail.me.com\",\"port\":993},\"default_from_name\":\"Your Name\",\"sender_emails\":[\"your@icloud.com\"]}}",
48
+ "DEFAULT_EMAIL_ACCOUNT": "icloud"
49
+ }
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ **Note**: Replace `/absolute/path/to/email-smtp-imap-mcp` with your actual installation path. You can also use a `.env` file instead of inline env config.
56
+
57
+ Restart Claude Desktop to load the MCP server.
58
+
59
+ ## 5 Tools
60
+
61
+ | Tool | Purpose |
62
+ |------|---------|
63
+ | `emails_find` | Search emails with filters |
64
+ | `emails_modify` | Mark read, archive, flag |
65
+ | `email_send` | Send new emails |
66
+ | `email_respond` | Reply or forward |
67
+ | `folders_list` | List folders |
68
+
69
+ ## Usage Examples
70
+
71
+ ```
72
+ "Find unread emails from last week"
73
+ "Send an email to team@company.com"
74
+ "Reply to the last email from John"
75
+ "Archive all emails older than 30 days"
76
+ "List my email folders"
77
+ ```
78
+
79
+ ## Documentation
80
+
81
+ - **QUICK_REFERENCE.md** - Command examples
82
+ - **DESIGN.md** - Architecture details
83
+ - **SETUP_COMPLETE.md** - Full setup guide
84
+
85
+ ## License
86
+
87
+ MIT
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Email account configuration management
3
+ */
4
+ /**
5
+ * Load email accounts from environment variables
6
+ */
7
+ export function loadAccounts() {
8
+ // Try JSON configuration first
9
+ const jsonConfig = process.env.EMAIL_ACCOUNTS_JSON;
10
+ if (jsonConfig) {
11
+ try {
12
+ return JSON.parse(jsonConfig);
13
+ }
14
+ catch (error) {
15
+ throw new Error(`Failed to parse EMAIL_ACCOUNTS_JSON: ${error}`);
16
+ }
17
+ }
18
+ // Fall back to single account from individual ENV vars
19
+ const singleAccount = {
20
+ smtp_host: process.env.SMTP_HOST || '',
21
+ smtp_port: parseInt(process.env.SMTP_PORT || '587'),
22
+ smtp_secure: process.env.SMTP_SECURE === 'true',
23
+ smtp_user: process.env.SMTP_USER || '',
24
+ smtp_pass: process.env.SMTP_PASS || '',
25
+ imap_host: process.env.IMAP_HOST || '',
26
+ imap_port: parseInt(process.env.IMAP_PORT || '993'),
27
+ imap_secure: process.env.IMAP_SECURE !== 'false', // Default to true
28
+ default_from_name: process.env.DEFAULT_FROM_NAME
29
+ };
30
+ // Validate single account has required fields
31
+ if (!singleAccount.smtp_host || !singleAccount.smtp_user || !singleAccount.smtp_pass) {
32
+ throw new Error('Email account configuration not found. Set EMAIL_ACCOUNTS_JSON or individual SMTP_* variables.');
33
+ }
34
+ // Use DEFAULT_EMAIL_ACCOUNT as the account name, or "default"
35
+ const accountName = process.env.DEFAULT_EMAIL_ACCOUNT || 'default';
36
+ return {
37
+ [accountName]: singleAccount
38
+ };
39
+ }
40
+ /**
41
+ * Get a specific email account by name
42
+ */
43
+ export function getAccount(accountName) {
44
+ const accounts = loadAccounts();
45
+ // If no account name specified, use the default
46
+ if (!accountName) {
47
+ const defaultAccountName = process.env.DEFAULT_EMAIL_ACCOUNT || Object.keys(accounts)[0];
48
+ if (!defaultAccountName) {
49
+ throw new Error('No email accounts configured');
50
+ }
51
+ accountName = defaultAccountName;
52
+ }
53
+ const config = accounts[accountName];
54
+ if (!config) {
55
+ const availableAccounts = Object.keys(accounts).join(', ');
56
+ throw new Error(`Account "${accountName}" not found. Available accounts: ${availableAccounts}`);
57
+ }
58
+ return { name: accountName, config };
59
+ }
60
+ /**
61
+ * List all configured accounts
62
+ */
63
+ export function listAccounts() {
64
+ const accounts = loadAccounts();
65
+ return Object.keys(accounts);
66
+ }
@@ -0,0 +1,238 @@
1
+ import fs from 'fs-extra';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { logToFile } from "./index.js";
5
+ // Define paths for configuration and data storage
6
+ export const CONFIG_DIR = path.join(os.homedir(), '.smtp-mcp-server');
7
+ export const TEMPLATES_DIR = path.join(CONFIG_DIR, 'templates');
8
+ export const SMTP_CONFIG_FILE = path.join(CONFIG_DIR, 'smtp-config.json');
9
+ export const LOG_FILE = path.join(CONFIG_DIR, 'email-logs.json');
10
+ // Default SMTP configuration
11
+ export const DEFAULT_SMTP_CONFIG = {
12
+ smtpServers: [
13
+ {
14
+ id: 'example-smtp',
15
+ name: 'Example SMTP',
16
+ host: 'smtp.example.com',
17
+ port: 587,
18
+ secure: false,
19
+ auth: {
20
+ user: 'username',
21
+ pass: 'password'
22
+ },
23
+ isDefault: true
24
+ }
25
+ ],
26
+ rateLimit: {
27
+ enabled: true,
28
+ messagesPerMinute: 30
29
+ }
30
+ };
31
+ // Default email template
32
+ export const DEFAULT_TEMPLATE = {
33
+ id: 'default',
34
+ name: 'Default Template',
35
+ subject: 'Default Subject',
36
+ body: 'Hello {{name}},\n\nThis is a default email template.\n\nBest regards,\nThe Team',
37
+ isDefault: true
38
+ };
39
+ // Example business template
40
+ export const BUSINESS_TEMPLATE = {
41
+ id: 'business-outreach',
42
+ name: 'Business Outreach',
43
+ subject: 'Partnership Opportunity - {{company}}',
44
+ body: `Dear {{name}},
45
+
46
+ I hope this email finds you well. I'm reaching out to explore potential collaboration opportunities between our organizations.
47
+
48
+ We've been following {{company}}'s achievements and believe there could be synergies worth exploring.
49
+
50
+ Would you be available for a brief call to discuss this further? I'd love to learn more about your current initiatives.
51
+
52
+ Best regards,
53
+ {{sender_name}}
54
+ {{sender_email}}`,
55
+ isDefault: false
56
+ };
57
+ // Example newsletter template
58
+ export const NEWSLETTER_TEMPLATE = {
59
+ id: 'newsletter',
60
+ name: 'Monthly Newsletter',
61
+ subject: '{{month}} Newsletter - {{company}}',
62
+ body: `Dear {{name}},
63
+
64
+ Welcome to our {{month}} newsletter!
65
+
66
+ {{main_content}}
67
+
68
+ We hope you found this update valuable. If you have any questions, please don't hesitate to contact us.
69
+
70
+ Best regards,
71
+ The {{company}} Team
72
+ {{contact_email}}`,
73
+ isDefault: false
74
+ };
75
+ /**
76
+ * Ensure all necessary directories and config files exist
77
+ */
78
+ export async function ensureConfigDirectories() {
79
+ try {
80
+ // Create config directory if it doesn't exist
81
+ await fs.ensureDir(CONFIG_DIR);
82
+ await fs.ensureDir(TEMPLATES_DIR);
83
+ // Create default SMTP config if it doesn't exist
84
+ if (!await fs.pathExists(SMTP_CONFIG_FILE)) {
85
+ await fs.writeJson(SMTP_CONFIG_FILE, DEFAULT_SMTP_CONFIG, { spaces: 2 });
86
+ }
87
+ // Create default template if it doesn't exist
88
+ const defaultTemplatePath = path.join(TEMPLATES_DIR, 'default.json');
89
+ if (!await fs.pathExists(defaultTemplatePath)) {
90
+ await fs.writeJson(defaultTemplatePath, DEFAULT_TEMPLATE, { spaces: 2 });
91
+ }
92
+ // Create business template if it doesn't exist
93
+ const businessTemplatePath = path.join(TEMPLATES_DIR, 'business-outreach.json');
94
+ if (!await fs.pathExists(businessTemplatePath)) {
95
+ await fs.writeJson(businessTemplatePath, BUSINESS_TEMPLATE, { spaces: 2 });
96
+ }
97
+ // Create newsletter template if it doesn't exist
98
+ const newsletterTemplatePath = path.join(TEMPLATES_DIR, 'newsletter.json');
99
+ if (!await fs.pathExists(newsletterTemplatePath)) {
100
+ await fs.writeJson(newsletterTemplatePath, NEWSLETTER_TEMPLATE, { spaces: 2 });
101
+ }
102
+ // Create log file if it doesn't exist
103
+ if (!await fs.pathExists(LOG_FILE)) {
104
+ await fs.writeJson(LOG_FILE, [], { spaces: 2 });
105
+ }
106
+ }
107
+ catch (error) {
108
+ logToFile(`Error ensuring config directories: ${error}`);
109
+ throw error;
110
+ }
111
+ }
112
+ /**
113
+ * Get SMTP configurations
114
+ */
115
+ export async function getSmtpConfigs() {
116
+ try {
117
+ const config = await fs.readJson(SMTP_CONFIG_FILE);
118
+ return config.smtpServers || [];
119
+ }
120
+ catch (error) {
121
+ logToFile('Error reading SMTP config:');
122
+ return DEFAULT_SMTP_CONFIG.smtpServers;
123
+ }
124
+ }
125
+ /**
126
+ * Get default SMTP configuration
127
+ */
128
+ export async function getDefaultSmtpConfig() {
129
+ const configs = await getSmtpConfigs();
130
+ return configs.find(config => config.isDefault) || configs[0] || DEFAULT_SMTP_CONFIG.smtpServers[0];
131
+ }
132
+ /**
133
+ * Save SMTP configurations
134
+ */
135
+ export async function saveSmtpConfigs(configs) {
136
+ try {
137
+ const currentConfig = await fs.readJson(SMTP_CONFIG_FILE);
138
+ currentConfig.smtpServers = configs;
139
+ await fs.writeJson(SMTP_CONFIG_FILE, currentConfig, { spaces: 2 });
140
+ return true;
141
+ }
142
+ catch (error) {
143
+ logToFile('Error saving SMTP config:');
144
+ return false;
145
+ }
146
+ }
147
+ /**
148
+ * Get email templates
149
+ */
150
+ export async function getEmailTemplates() {
151
+ try {
152
+ const files = await fs.readdir(TEMPLATES_DIR);
153
+ const templates = [];
154
+ for (const file of files) {
155
+ if (file.endsWith('.json')) {
156
+ const templatePath = path.join(TEMPLATES_DIR, file);
157
+ const template = await fs.readJson(templatePath);
158
+ templates.push(template);
159
+ }
160
+ }
161
+ return templates;
162
+ }
163
+ catch (error) {
164
+ logToFile('Error reading email templates:');
165
+ return [DEFAULT_TEMPLATE, BUSINESS_TEMPLATE, NEWSLETTER_TEMPLATE];
166
+ }
167
+ }
168
+ /**
169
+ * Get default email template
170
+ */
171
+ export async function getDefaultEmailTemplate() {
172
+ const templates = await getEmailTemplates();
173
+ return templates.find(template => template.isDefault) || templates[0] || DEFAULT_TEMPLATE;
174
+ }
175
+ /**
176
+ * Save email template
177
+ */
178
+ export async function saveEmailTemplate(template) {
179
+ try {
180
+ const templatePath = path.join(TEMPLATES_DIR, `${template.id}.json`);
181
+ await fs.writeJson(templatePath, template, { spaces: 2 });
182
+ return true;
183
+ }
184
+ catch (error) {
185
+ logToFile('Error saving email template:');
186
+ return false;
187
+ }
188
+ }
189
+ /**
190
+ * Delete email template
191
+ */
192
+ export async function deleteEmailTemplate(templateId) {
193
+ try {
194
+ const templatePath = path.join(TEMPLATES_DIR, `${templateId}.json`);
195
+ await fs.remove(templatePath);
196
+ return true;
197
+ }
198
+ catch (error) {
199
+ logToFile('Error deleting email template:');
200
+ return false;
201
+ }
202
+ }
203
+ /**
204
+ * Log email activity
205
+ */
206
+ export async function logEmailActivity(entry) {
207
+ try {
208
+ let logs = [];
209
+ // Read existing logs if file exists
210
+ if (await fs.pathExists(LOG_FILE)) {
211
+ logs = await fs.readJson(LOG_FILE);
212
+ }
213
+ // Add new log entry
214
+ logs.push(entry);
215
+ // Write updated logs
216
+ await fs.writeJson(LOG_FILE, logs, { spaces: 2 });
217
+ return true;
218
+ }
219
+ catch (error) {
220
+ logToFile('Error logging email activity:');
221
+ return false;
222
+ }
223
+ }
224
+ /**
225
+ * Get email logs
226
+ */
227
+ export async function getEmailLogs() {
228
+ try {
229
+ if (await fs.pathExists(LOG_FILE)) {
230
+ return await fs.readJson(LOG_FILE);
231
+ }
232
+ return [];
233
+ }
234
+ catch (error) {
235
+ logToFile('Error reading email logs:');
236
+ return [];
237
+ }
238
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Tool request handlers for email operations
3
+ */
4
+ import { getAccount } from './accountManager.js';
5
+ import { searchEmails, modifyEmails, listFolders } from './imapService.js';
6
+ import { sendEmail, replyToEmail, forwardEmail } from './smtpService.js';
7
+ /**
8
+ * Handle emails_find tool
9
+ */
10
+ export async function handleEmailsFind(args) {
11
+ try {
12
+ const { name, config } = getAccount(args.account_name);
13
+ const filters = args.filters || {};
14
+ const limit = args.limit || 20;
15
+ const includeContent = args.include_content || false;
16
+ const includeAttachments = args.include_attachments || false;
17
+ const emails = await searchEmails(config, filters, limit, includeContent, includeAttachments);
18
+ return JSON.stringify({
19
+ success: true,
20
+ account: name,
21
+ count: emails.length,
22
+ emails
23
+ }, null, 2);
24
+ }
25
+ catch (error) {
26
+ return JSON.stringify({
27
+ success: false,
28
+ error: error.message || 'Failed to search emails'
29
+ }, null, 2);
30
+ }
31
+ }
32
+ /**
33
+ * Handle emails_modify tool
34
+ */
35
+ export async function handleEmailsModify(args) {
36
+ try {
37
+ const { name, config } = getAccount(args.account_name);
38
+ if (!args.email_ids || args.email_ids.length === 0) {
39
+ throw new Error('email_ids is required and must not be empty');
40
+ }
41
+ const result = await modifyEmails(config, args.email_ids, {
42
+ markRead: args.mark_read,
43
+ markUnread: args.mark_unread,
44
+ flag: args.flag,
45
+ unflag: args.unflag,
46
+ moveToFolder: args.move_to_folder
47
+ });
48
+ return JSON.stringify({
49
+ success: result.success,
50
+ account: name,
51
+ modified: result.modified,
52
+ total: args.email_ids.length,
53
+ errors: result.errors
54
+ }, null, 2);
55
+ }
56
+ catch (error) {
57
+ return JSON.stringify({
58
+ success: false,
59
+ error: error.message || 'Failed to modify emails'
60
+ }, null, 2);
61
+ }
62
+ }
63
+ /**
64
+ * Handle email_send tool
65
+ */
66
+ export async function handleEmailSend(args) {
67
+ try {
68
+ const { name, config } = getAccount(args.account_name);
69
+ if (!args.to || args.to.length === 0) {
70
+ throw new Error('to is required and must not be empty');
71
+ }
72
+ if (!args.subject) {
73
+ throw new Error('subject is required');
74
+ }
75
+ if (!args.body) {
76
+ throw new Error('body is required');
77
+ }
78
+ const result = await sendEmail(config, {
79
+ to: args.to,
80
+ subject: args.subject,
81
+ body: args.body,
82
+ bodyType: args.body_type || 'html',
83
+ cc: args.cc,
84
+ bcc: args.bcc,
85
+ attachments: args.attachments,
86
+ fromName: config.default_from_name
87
+ });
88
+ return JSON.stringify({
89
+ success: result.success,
90
+ account: name,
91
+ message_id: result.messageId,
92
+ to: args.to,
93
+ subject: args.subject
94
+ }, null, 2);
95
+ }
96
+ catch (error) {
97
+ return JSON.stringify({
98
+ success: false,
99
+ error: error.message || 'Failed to send email'
100
+ }, null, 2);
101
+ }
102
+ }
103
+ /**
104
+ * Handle email_respond tool
105
+ */
106
+ export async function handleEmailRespond(args) {
107
+ try {
108
+ const { name, config } = getAccount(args.account_name);
109
+ if (!args.email_id) {
110
+ throw new Error('email_id is required');
111
+ }
112
+ if (!args.body) {
113
+ throw new Error('body is required');
114
+ }
115
+ const responseType = args.response_type || 'reply';
116
+ let result;
117
+ if (responseType === 'forward') {
118
+ if (!args.to || args.to.length === 0) {
119
+ throw new Error('to is required for forward');
120
+ }
121
+ result = await forwardEmail(config, args.email_id, {
122
+ to: args.to,
123
+ body: args.body,
124
+ bodyType: args.body_type || 'html',
125
+ includeOriginal: args.include_original !== false,
126
+ includeAttachments: args.include_attachments !== false,
127
+ additionalAttachments: args.additional_attachments
128
+ });
129
+ }
130
+ else {
131
+ result = await replyToEmail(config, args.email_id, {
132
+ body: args.body,
133
+ bodyType: args.body_type || 'html',
134
+ replyAll: responseType === 'reply_all',
135
+ includeOriginal: args.include_original !== false,
136
+ includeAttachments: args.include_attachments !== false,
137
+ additionalAttachments: args.additional_attachments
138
+ });
139
+ }
140
+ return JSON.stringify({
141
+ success: result.success,
142
+ account: name,
143
+ message_id: result.messageId,
144
+ response_type: responseType,
145
+ original_email_id: args.email_id
146
+ }, null, 2);
147
+ }
148
+ catch (error) {
149
+ return JSON.stringify({
150
+ success: false,
151
+ error: error.message || 'Failed to respond to email'
152
+ }, null, 2);
153
+ }
154
+ }
155
+ /**
156
+ * Handle folders_list tool
157
+ */
158
+ export async function handleFoldersList(args) {
159
+ try {
160
+ const { name, config } = getAccount(args.account_name);
161
+ const folders = await listFolders(config, args.include_counts || false);
162
+ return JSON.stringify({
163
+ success: true,
164
+ account: name,
165
+ count: folders.length,
166
+ folders
167
+ }, null, 2);
168
+ }
169
+ catch (error) {
170
+ return JSON.stringify({
171
+ success: false,
172
+ error: error.message || 'Failed to list folders'
173
+ }, null, 2);
174
+ }
175
+ }