@easyasstudio/imap-email-mcp 1.1.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 +40 -0
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/index.js +841 -0
- package/package.json +50 -0
package/.env.example
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# IMAP Email MCP Server Configuration
|
|
2
|
+
# Copy this file to .env and fill in your values
|
|
3
|
+
|
|
4
|
+
# Required - IMAP Settings
|
|
5
|
+
IMAP_USER=your-email@example.com
|
|
6
|
+
IMAP_PASSWORD=your-app-password
|
|
7
|
+
IMAP_HOST=imap.example.com
|
|
8
|
+
|
|
9
|
+
# Optional - IMAP Settings (defaults shown)
|
|
10
|
+
# IMAP_PORT=993
|
|
11
|
+
# IMAP_TLS=true
|
|
12
|
+
# IMAP_AUTH_TIMEOUT=10000
|
|
13
|
+
# IMAP_TLS_REJECT_UNAUTHORIZED=true
|
|
14
|
+
|
|
15
|
+
# Optional - SMTP Settings (defaults to IMAP values if not set)
|
|
16
|
+
# SMTP_HOST=smtp.example.com
|
|
17
|
+
# SMTP_PORT=465
|
|
18
|
+
# SMTP_SECURE=true
|
|
19
|
+
# SMTP_USER=your-email@example.com
|
|
20
|
+
# SMTP_PASSWORD=your-app-password
|
|
21
|
+
|
|
22
|
+
# Common Provider Settings:
|
|
23
|
+
#
|
|
24
|
+
# Gmail:
|
|
25
|
+
# IMAP_HOST=imap.gmail.com
|
|
26
|
+
# SMTP_HOST=smtp.gmail.com
|
|
27
|
+
#
|
|
28
|
+
# Outlook:
|
|
29
|
+
# IMAP_HOST=outlook.office365.com
|
|
30
|
+
# SMTP_HOST=smtp.office365.com
|
|
31
|
+
# SMTP_PORT=587
|
|
32
|
+
# SMTP_SECURE=false
|
|
33
|
+
#
|
|
34
|
+
# Yahoo:
|
|
35
|
+
# IMAP_HOST=imap.mail.yahoo.com
|
|
36
|
+
# SMTP_HOST=smtp.mail.yahoo.com
|
|
37
|
+
#
|
|
38
|
+
# Fastmail:
|
|
39
|
+
# IMAP_HOST=imap.fastmail.com
|
|
40
|
+
# SMTP_HOST=smtp.fastmail.com
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# IMAP Email MCP Server
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that provides email capabilities to Claude Code, Claude Desktop, Cursor, and other MCP-compatible AI tools. Connect to any IMAP/SMTP email provider to read, search, compose, and manage emails directly from your AI assistant.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Claude Code (CLI)
|
|
8
|
+
|
|
9
|
+
**Important:** Claude Code CLI uses `claude mcp add`, not config files.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add imap-email -s user \
|
|
13
|
+
-e IMAP_USER=you@example.com \
|
|
14
|
+
-e IMAP_PASSWORD='your-app-password' \
|
|
15
|
+
-e IMAP_HOST=imap.example.com \
|
|
16
|
+
-- npx -y imap-email-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> **Note:** If your password contains special shell characters (`%`, `^`, `*`, `$`, `!`, etc.), wrap it in single quotes as shown above.
|
|
20
|
+
|
|
21
|
+
> **Note:** Restart Claude Code after adding an MCP for the new tools to become available.
|
|
22
|
+
|
|
23
|
+
Verify with:
|
|
24
|
+
```bash
|
|
25
|
+
claude mcp list
|
|
26
|
+
claude mcp get imap-email
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Remove with:
|
|
30
|
+
```bash
|
|
31
|
+
claude mcp remove imap-email -s user
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Cursor
|
|
35
|
+
|
|
36
|
+
Add new MCP server:
|
|
37
|
+
- **Name:** `imap-email`
|
|
38
|
+
- **Type:** `command`
|
|
39
|
+
- **Command:** `npx -y imap-email-mcp`
|
|
40
|
+
|
|
41
|
+
Then set environment variables in Cursor's MCP settings:
|
|
42
|
+
```
|
|
43
|
+
IMAP_USER=your-email@example.com
|
|
44
|
+
IMAP_PASSWORD=your-app-password
|
|
45
|
+
IMAP_HOST=imap.example.com
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Claude Desktop
|
|
49
|
+
|
|
50
|
+
Add to your config file:
|
|
51
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
52
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"imap-email": {
|
|
58
|
+
"command": "npx",
|
|
59
|
+
"args": ["-y", "imap-email-mcp"],
|
|
60
|
+
"env": {
|
|
61
|
+
"IMAP_USER": "your-email@example.com",
|
|
62
|
+
"IMAP_PASSWORD": "your-app-password",
|
|
63
|
+
"IMAP_HOST": "imap.example.com"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- **Read emails** - List and read emails from any folder
|
|
73
|
+
- **Search** - Search by subject, sender, or body content
|
|
74
|
+
- **Compose** - Create and save email drafts
|
|
75
|
+
- **Send** - Send emails directly via SMTP
|
|
76
|
+
- **Manage drafts** - List, read, update, and delete drafts
|
|
77
|
+
- **Delete emails** - Remove unwanted messages
|
|
78
|
+
- **Multi-provider support** - Works with Gmail, Outlook, Yahoo, Fastmail, and any standard IMAP provider
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
### Required Environment Variables
|
|
83
|
+
|
|
84
|
+
| Variable | Description |
|
|
85
|
+
|----------|-------------|
|
|
86
|
+
| `IMAP_USER` | Your email address |
|
|
87
|
+
| `IMAP_PASSWORD` | App password (not your main password!) |
|
|
88
|
+
| `IMAP_HOST` | IMAP server hostname |
|
|
89
|
+
|
|
90
|
+
### Optional Environment Variables
|
|
91
|
+
|
|
92
|
+
| Variable | Default | Description |
|
|
93
|
+
|----------|---------|-------------|
|
|
94
|
+
| `IMAP_PORT` | `993` | IMAP port |
|
|
95
|
+
| `IMAP_TLS` | `true` | Use TLS |
|
|
96
|
+
| `SMTP_HOST` | Same as IMAP_HOST | SMTP server hostname |
|
|
97
|
+
| `SMTP_PORT` | `465` | SMTP port |
|
|
98
|
+
| `SMTP_SECURE` | `true` | Use secure SMTP |
|
|
99
|
+
|
|
100
|
+
### Provider Settings
|
|
101
|
+
|
|
102
|
+
| Provider | IMAP_HOST | SMTP_HOST | Notes |
|
|
103
|
+
|----------|-----------|-----------|-------|
|
|
104
|
+
| **Gmail** | `imap.gmail.com` | `smtp.gmail.com` | [Create App Password](https://myaccount.google.com/apppasswords) |
|
|
105
|
+
| **Outlook** | `outlook.office365.com` | `smtp.office365.com` | Use port 587, SMTP_SECURE=false |
|
|
106
|
+
| **Yahoo** | `imap.mail.yahoo.com` | `smtp.mail.yahoo.com` | Generate App Password in settings |
|
|
107
|
+
| **Fastmail** | `imap.fastmail.com` | `smtp.fastmail.com` | App Password from Privacy & Security |
|
|
108
|
+
| **iCloud** | `imap.mail.me.com` | `smtp.mail.me.com` | [Generate App Password](https://appleid.apple.com/) |
|
|
109
|
+
|
|
110
|
+
## Available Tools
|
|
111
|
+
|
|
112
|
+
| Tool | Description |
|
|
113
|
+
|------|-------------|
|
|
114
|
+
| `list_folders` | List all email folders/mailboxes |
|
|
115
|
+
| `list_emails` | List emails with optional filtering |
|
|
116
|
+
| `get_email` | Get full email content by UID |
|
|
117
|
+
| `search_emails` | Search by subject, sender, or body |
|
|
118
|
+
| `list_drafts` | List all draft emails |
|
|
119
|
+
| `get_draft` | Get a specific draft by UID |
|
|
120
|
+
| `create_draft` | Create a new email draft |
|
|
121
|
+
| `update_draft` | Update an existing draft |
|
|
122
|
+
| `send_email` | Send an email directly |
|
|
123
|
+
| `delete_email` | Delete an email by UID |
|
|
124
|
+
|
|
125
|
+
## Usage Examples
|
|
126
|
+
|
|
127
|
+
Once configured, use natural language:
|
|
128
|
+
|
|
129
|
+
- "Check my inbox for unread emails"
|
|
130
|
+
- "Search for emails from john@example.com"
|
|
131
|
+
- "Create a draft email to sarah@example.com about the meeting tomorrow"
|
|
132
|
+
- "Show me my drafts folder"
|
|
133
|
+
|
|
134
|
+
## Security Best Practices
|
|
135
|
+
|
|
136
|
+
1. **Use App Passwords** - Never use your main account password
|
|
137
|
+
2. **Environment Variables** - Store credentials in env vars, not in code
|
|
138
|
+
3. **Review Before Sending** - Use `create_draft` instead of `send_email` to review first
|
|
139
|
+
|
|
140
|
+
## Troubleshooting
|
|
141
|
+
|
|
142
|
+
**Authentication failed**
|
|
143
|
+
- Verify your app password is correct
|
|
144
|
+
- Ensure IMAP access is enabled in your email provider's settings
|
|
145
|
+
|
|
146
|
+
**Drafts folder not found**
|
|
147
|
+
- The server tries common names (`Drafts`, `INBOX.Drafts`, `[Gmail]/Drafts`)
|
|
148
|
+
- Your provider may use a different folder name
|
|
149
|
+
|
|
150
|
+
**Connection timeout**
|
|
151
|
+
- Check your `IMAP_HOST` is correct
|
|
152
|
+
- Verify port 993 is not blocked by firewall
|
|
153
|
+
|
|
154
|
+
## Alternative Installation
|
|
155
|
+
|
|
156
|
+
### Install globally
|
|
157
|
+
```bash
|
|
158
|
+
npm install -g imap-email-mcp
|
|
159
|
+
imap-email-mcp
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Clone and run
|
|
163
|
+
```bash
|
|
164
|
+
git clone https://github.com/jdickey1/imap-email-mcp.git
|
|
165
|
+
cd imap-email-mcp
|
|
166
|
+
npm install
|
|
167
|
+
node index.js
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
173
|
+
|
|
174
|
+
## Links
|
|
175
|
+
|
|
176
|
+
- [npm package](https://www.npmjs.com/package/imap-email-mcp)
|
|
177
|
+
- [GitHub repo](https://github.com/jdickey1/imap-email-mcp)
|
|
178
|
+
- [MCP Protocol](https://modelcontextprotocol.io/)
|
package/index.js
ADDED
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IMAP Email MCP Server for Claude Code
|
|
5
|
+
*
|
|
6
|
+
* A Model Context Protocol (MCP) server that provides email capabilities
|
|
7
|
+
* through any IMAP/SMTP email provider.
|
|
8
|
+
*
|
|
9
|
+
* @license MIT
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
13
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
+
import {
|
|
15
|
+
CallToolRequestSchema,
|
|
16
|
+
ListToolsRequestSchema,
|
|
17
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
18
|
+
import imaps from 'imap-simple';
|
|
19
|
+
import { simpleParser } from 'mailparser';
|
|
20
|
+
import nodemailer from 'nodemailer';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
|
|
24
|
+
// Configuration from environment variables
|
|
25
|
+
const IMAP_CONFIG = {
|
|
26
|
+
imap: {
|
|
27
|
+
user: process.env.IMAP_USER,
|
|
28
|
+
password: process.env.IMAP_PASSWORD,
|
|
29
|
+
host: process.env.IMAP_HOST,
|
|
30
|
+
port: parseInt(process.env.IMAP_PORT || '993'),
|
|
31
|
+
tls: process.env.IMAP_TLS !== 'false',
|
|
32
|
+
authTimeout: parseInt(process.env.IMAP_AUTH_TIMEOUT || '10000'),
|
|
33
|
+
tlsOptions: {
|
|
34
|
+
rejectUnauthorized: process.env.IMAP_TLS_REJECT_UNAUTHORIZED !== 'false'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const SMTP_CONFIG = {
|
|
40
|
+
host: process.env.SMTP_HOST,
|
|
41
|
+
port: parseInt(process.env.SMTP_PORT || '465'),
|
|
42
|
+
secure: process.env.SMTP_SECURE !== 'false',
|
|
43
|
+
auth: {
|
|
44
|
+
user: process.env.SMTP_USER || process.env.IMAP_USER,
|
|
45
|
+
pass: process.env.SMTP_PASSWORD || process.env.IMAP_PASSWORD
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Validate required configuration
|
|
50
|
+
function validateConfig() {
|
|
51
|
+
const required = ['IMAP_USER', 'IMAP_PASSWORD', 'IMAP_HOST'];
|
|
52
|
+
const missing = required.filter(key => !process.env[key]);
|
|
53
|
+
|
|
54
|
+
if (missing.length > 0) {
|
|
55
|
+
console.error(`Missing required environment variables: ${missing.join(', ')}`);
|
|
56
|
+
console.error('Please set these variables before starting the server.');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// SMTP host defaults to IMAP host if not set
|
|
61
|
+
if (!process.env.SMTP_HOST) {
|
|
62
|
+
SMTP_CONFIG.host = process.env.IMAP_HOST;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const server = new Server(
|
|
67
|
+
{
|
|
68
|
+
name: 'imap-email-mcp',
|
|
69
|
+
version: '1.0.0',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
capabilities: {
|
|
73
|
+
tools: {},
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Tool definitions
|
|
79
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
80
|
+
return {
|
|
81
|
+
tools: [
|
|
82
|
+
{
|
|
83
|
+
name: 'list_folders',
|
|
84
|
+
description: 'List all email folders/mailboxes in the IMAP account',
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {},
|
|
88
|
+
required: []
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'list_emails',
|
|
93
|
+
description: 'List emails from a folder with optional filtering',
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {
|
|
97
|
+
folder: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description: 'Folder name (default: INBOX)',
|
|
100
|
+
default: 'INBOX'
|
|
101
|
+
},
|
|
102
|
+
limit: {
|
|
103
|
+
type: 'number',
|
|
104
|
+
description: 'Maximum number of emails to return (default: 20)',
|
|
105
|
+
default: 20
|
|
106
|
+
},
|
|
107
|
+
unseen_only: {
|
|
108
|
+
type: 'boolean',
|
|
109
|
+
description: 'Only return unread emails',
|
|
110
|
+
default: false
|
|
111
|
+
},
|
|
112
|
+
since_date: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: 'Only return emails since this date (YYYY-MM-DD format)'
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
required: []
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'get_email',
|
|
122
|
+
description: 'Get full email content by UID',
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
uid: {
|
|
127
|
+
type: 'number',
|
|
128
|
+
description: 'Email UID'
|
|
129
|
+
},
|
|
130
|
+
folder: {
|
|
131
|
+
type: 'string',
|
|
132
|
+
description: 'Folder name (default: INBOX)',
|
|
133
|
+
default: 'INBOX'
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
required: ['uid']
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'search_emails',
|
|
141
|
+
description: 'Search emails by subject, from, or body text',
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
properties: {
|
|
145
|
+
folder: {
|
|
146
|
+
type: 'string',
|
|
147
|
+
description: 'Folder to search (default: INBOX)',
|
|
148
|
+
default: 'INBOX'
|
|
149
|
+
},
|
|
150
|
+
subject: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
description: 'Search in subject line'
|
|
153
|
+
},
|
|
154
|
+
from: {
|
|
155
|
+
type: 'string',
|
|
156
|
+
description: 'Search by sender'
|
|
157
|
+
},
|
|
158
|
+
body: {
|
|
159
|
+
type: 'string',
|
|
160
|
+
description: 'Search in body text'
|
|
161
|
+
},
|
|
162
|
+
limit: {
|
|
163
|
+
type: 'number',
|
|
164
|
+
description: 'Maximum results (default: 20)',
|
|
165
|
+
default: 20
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
required: []
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'list_drafts',
|
|
173
|
+
description: 'List all draft emails',
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
limit: {
|
|
178
|
+
type: 'number',
|
|
179
|
+
description: 'Maximum number of drafts to return (default: 20)',
|
|
180
|
+
default: 20
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
required: []
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'get_draft',
|
|
188
|
+
description: 'Get a specific draft email by UID',
|
|
189
|
+
inputSchema: {
|
|
190
|
+
type: 'object',
|
|
191
|
+
properties: {
|
|
192
|
+
uid: {
|
|
193
|
+
type: 'number',
|
|
194
|
+
description: 'Draft UID'
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
required: ['uid']
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'create_draft',
|
|
202
|
+
description: 'Create a new draft email',
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
to: {
|
|
207
|
+
type: 'string',
|
|
208
|
+
description: 'Recipient email address(es), comma-separated'
|
|
209
|
+
},
|
|
210
|
+
subject: {
|
|
211
|
+
type: 'string',
|
|
212
|
+
description: 'Email subject'
|
|
213
|
+
},
|
|
214
|
+
body: {
|
|
215
|
+
type: 'string',
|
|
216
|
+
description: 'Email body (plain text)'
|
|
217
|
+
},
|
|
218
|
+
html: {
|
|
219
|
+
type: 'string',
|
|
220
|
+
description: 'Email body (HTML)'
|
|
221
|
+
},
|
|
222
|
+
cc: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
description: 'CC recipients, comma-separated'
|
|
225
|
+
},
|
|
226
|
+
bcc: {
|
|
227
|
+
type: 'string',
|
|
228
|
+
description: 'BCC recipients, comma-separated'
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
required: ['to', 'subject']
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'update_draft',
|
|
236
|
+
description: 'Update an existing draft by deleting old and creating new',
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: 'object',
|
|
239
|
+
properties: {
|
|
240
|
+
uid: {
|
|
241
|
+
type: 'number',
|
|
242
|
+
description: 'UID of draft to update'
|
|
243
|
+
},
|
|
244
|
+
to: {
|
|
245
|
+
type: 'string',
|
|
246
|
+
description: 'Recipient email address(es)'
|
|
247
|
+
},
|
|
248
|
+
subject: {
|
|
249
|
+
type: 'string',
|
|
250
|
+
description: 'Email subject'
|
|
251
|
+
},
|
|
252
|
+
body: {
|
|
253
|
+
type: 'string',
|
|
254
|
+
description: 'Email body (plain text)'
|
|
255
|
+
},
|
|
256
|
+
html: {
|
|
257
|
+
type: 'string',
|
|
258
|
+
description: 'Email body (HTML)'
|
|
259
|
+
},
|
|
260
|
+
cc: {
|
|
261
|
+
type: 'string',
|
|
262
|
+
description: 'CC recipients'
|
|
263
|
+
},
|
|
264
|
+
bcc: {
|
|
265
|
+
type: 'string',
|
|
266
|
+
description: 'BCC recipients'
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
required: ['uid', 'to', 'subject']
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
name: 'send_email',
|
|
274
|
+
description: 'Send an email directly',
|
|
275
|
+
inputSchema: {
|
|
276
|
+
type: 'object',
|
|
277
|
+
properties: {
|
|
278
|
+
to: {
|
|
279
|
+
type: 'string',
|
|
280
|
+
description: 'Recipient email address(es)'
|
|
281
|
+
},
|
|
282
|
+
subject: {
|
|
283
|
+
type: 'string',
|
|
284
|
+
description: 'Email subject'
|
|
285
|
+
},
|
|
286
|
+
body: {
|
|
287
|
+
type: 'string',
|
|
288
|
+
description: 'Email body (plain text)'
|
|
289
|
+
},
|
|
290
|
+
html: {
|
|
291
|
+
type: 'string',
|
|
292
|
+
description: 'Email body (HTML)'
|
|
293
|
+
},
|
|
294
|
+
cc: {
|
|
295
|
+
type: 'string',
|
|
296
|
+
description: 'CC recipients'
|
|
297
|
+
},
|
|
298
|
+
bcc: {
|
|
299
|
+
type: 'string',
|
|
300
|
+
description: 'BCC recipients'
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
required: ['to', 'subject']
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: 'download_attachment',
|
|
308
|
+
description: 'Download a specific attachment from an email to a local file',
|
|
309
|
+
inputSchema: {
|
|
310
|
+
type: 'object',
|
|
311
|
+
properties: {
|
|
312
|
+
uid: {
|
|
313
|
+
type: 'number',
|
|
314
|
+
description: 'Email UID'
|
|
315
|
+
},
|
|
316
|
+
attachment_index: {
|
|
317
|
+
type: 'number',
|
|
318
|
+
description: 'Attachment index (0-based, default: 0)',
|
|
319
|
+
default: 0
|
|
320
|
+
},
|
|
321
|
+
filename: {
|
|
322
|
+
type: 'string',
|
|
323
|
+
description: 'Custom filename for the downloaded file (optional)'
|
|
324
|
+
},
|
|
325
|
+
output_dir: {
|
|
326
|
+
type: 'string',
|
|
327
|
+
description: 'Output directory (default: working directory)'
|
|
328
|
+
},
|
|
329
|
+
folder: {
|
|
330
|
+
type: 'string',
|
|
331
|
+
description: 'Folder name (default: INBOX)',
|
|
332
|
+
default: 'INBOX'
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
required: ['uid']
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: 'delete_email',
|
|
340
|
+
description: 'Delete an email by UID',
|
|
341
|
+
inputSchema: {
|
|
342
|
+
type: 'object',
|
|
343
|
+
properties: {
|
|
344
|
+
uid: {
|
|
345
|
+
type: 'number',
|
|
346
|
+
description: 'Email UID to delete'
|
|
347
|
+
},
|
|
348
|
+
folder: {
|
|
349
|
+
type: 'string',
|
|
350
|
+
description: 'Folder name (default: INBOX)',
|
|
351
|
+
default: 'INBOX'
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
required: ['uid']
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
};
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Helper function to connect to IMAP
|
|
362
|
+
async function connectIMAP() {
|
|
363
|
+
if (!IMAP_CONFIG.imap.password) {
|
|
364
|
+
throw new Error('IMAP_PASSWORD environment variable is not set');
|
|
365
|
+
}
|
|
366
|
+
const connection = await imaps.connect(IMAP_CONFIG);
|
|
367
|
+
// Send IMAP ID command (required by 163.com and other Chinese email providers)
|
|
368
|
+
await new Promise((resolve, reject) => {
|
|
369
|
+
connection.imap._enqueue('ID ("name" "imap-email-mcp" "version" "1.0" "vendor" "OpenClaw")');
|
|
370
|
+
connection.imap.once('close', reject);
|
|
371
|
+
setTimeout(resolve, 500);
|
|
372
|
+
});
|
|
373
|
+
return connection;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Helper to find drafts folder (handles different provider conventions)
|
|
377
|
+
async function findDraftsFolder(connection) {
|
|
378
|
+
const boxes = await connection.getBoxes();
|
|
379
|
+
|
|
380
|
+
// Common drafts folder names across providers
|
|
381
|
+
const draftNames = [
|
|
382
|
+
'Drafts',
|
|
383
|
+
'INBOX.Drafts',
|
|
384
|
+
'[Gmail]/Drafts',
|
|
385
|
+
'[Google Mail]/Drafts',
|
|
386
|
+
'Draft',
|
|
387
|
+
'INBOX/Drafts'
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
for (const name of draftNames) {
|
|
391
|
+
if (boxes[name] || name.split('.').reduce((acc, part) => acc?.[part], boxes)) {
|
|
392
|
+
return name;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Check for nested Drafts under INBOX
|
|
397
|
+
if (boxes.INBOX && boxes.INBOX.children && boxes.INBOX.children.Drafts) {
|
|
398
|
+
return 'INBOX.Drafts';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return 'Drafts'; // Default fallback
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Tool handlers
|
|
405
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
406
|
+
const { name, arguments: args } = request.params;
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
switch (name) {
|
|
410
|
+
case 'list_folders': {
|
|
411
|
+
const connection = await connectIMAP();
|
|
412
|
+
try {
|
|
413
|
+
const boxes = await connection.getBoxes();
|
|
414
|
+
const folders = [];
|
|
415
|
+
|
|
416
|
+
function extractFolders(obj, prefix = '') {
|
|
417
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
418
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
419
|
+
folders.push(fullPath);
|
|
420
|
+
if (value.children) {
|
|
421
|
+
extractFolders(value.children, fullPath);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
extractFolders(boxes);
|
|
427
|
+
return { content: [{ type: 'text', text: JSON.stringify(folders, null, 2) }] };
|
|
428
|
+
} finally {
|
|
429
|
+
connection.end();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
case 'list_emails': {
|
|
434
|
+
const folder = args.folder || 'INBOX';
|
|
435
|
+
const limit = args.limit || 20;
|
|
436
|
+
const connection = await connectIMAP();
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
await connection.openBox(folder);
|
|
440
|
+
|
|
441
|
+
let searchCriteria = ['ALL'];
|
|
442
|
+
if (args.unseen_only) {
|
|
443
|
+
searchCriteria = ['UNSEEN'];
|
|
444
|
+
}
|
|
445
|
+
if (args.since_date) {
|
|
446
|
+
searchCriteria = [['SINCE', args.since_date]];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const fetchOptions = {
|
|
450
|
+
bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)'],
|
|
451
|
+
struct: true
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const messages = await connection.search(searchCriteria, fetchOptions);
|
|
455
|
+
const results = messages.slice(-limit).reverse().map(msg => {
|
|
456
|
+
const header = msg.parts.find(p => p.which.includes('HEADER'))?.body || {};
|
|
457
|
+
return {
|
|
458
|
+
uid: msg.attributes.uid,
|
|
459
|
+
date: header.date?.[0],
|
|
460
|
+
from: header.from?.[0],
|
|
461
|
+
to: header.to?.[0],
|
|
462
|
+
subject: header.subject?.[0],
|
|
463
|
+
flags: msg.attributes.flags
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
468
|
+
} finally {
|
|
469
|
+
connection.end();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
case 'get_email': {
|
|
474
|
+
const folder = args.folder || 'INBOX';
|
|
475
|
+
const connection = await connectIMAP();
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
await connection.openBox(folder);
|
|
479
|
+
|
|
480
|
+
const fetchOptions = {
|
|
481
|
+
bodies: [''],
|
|
482
|
+
struct: true
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const messages = await connection.search([['UID', args.uid]], fetchOptions);
|
|
486
|
+
|
|
487
|
+
if (messages.length === 0) {
|
|
488
|
+
return { content: [{ type: 'text', text: 'Email not found' }] };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const msg = messages[0];
|
|
492
|
+
const rawBody = msg.parts.find(p => p.which === '')?.body;
|
|
493
|
+
const parsed = await simpleParser(rawBody);
|
|
494
|
+
|
|
495
|
+
return {
|
|
496
|
+
content: [{
|
|
497
|
+
type: 'text',
|
|
498
|
+
text: JSON.stringify({
|
|
499
|
+
uid: msg.attributes.uid,
|
|
500
|
+
from: parsed.from?.text,
|
|
501
|
+
to: parsed.to?.text,
|
|
502
|
+
cc: parsed.cc?.text,
|
|
503
|
+
subject: parsed.subject,
|
|
504
|
+
date: parsed.date,
|
|
505
|
+
text: parsed.text,
|
|
506
|
+
html: parsed.html,
|
|
507
|
+
attachments: parsed.attachments?.map(a => ({
|
|
508
|
+
filename: a.filename,
|
|
509
|
+
contentType: a.contentType,
|
|
510
|
+
size: a.size
|
|
511
|
+
}))
|
|
512
|
+
}, null, 2)
|
|
513
|
+
}]
|
|
514
|
+
};
|
|
515
|
+
} finally {
|
|
516
|
+
connection.end();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
case 'search_emails': {
|
|
521
|
+
const folder = args.folder || 'INBOX';
|
|
522
|
+
const limit = args.limit || 20;
|
|
523
|
+
const connection = await connectIMAP();
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
await connection.openBox(folder);
|
|
527
|
+
|
|
528
|
+
let searchCriteria = [];
|
|
529
|
+
if (args.subject) searchCriteria.push(['SUBJECT', args.subject]);
|
|
530
|
+
if (args.from) searchCriteria.push(['FROM', args.from]);
|
|
531
|
+
if (args.body) searchCriteria.push(['BODY', args.body]);
|
|
532
|
+
|
|
533
|
+
if (searchCriteria.length === 0) {
|
|
534
|
+
searchCriteria = ['ALL'];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const fetchOptions = {
|
|
538
|
+
bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)'],
|
|
539
|
+
struct: true
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const messages = await connection.search(searchCriteria, fetchOptions);
|
|
543
|
+
const results = messages.slice(-limit).reverse().map(msg => {
|
|
544
|
+
const header = msg.parts.find(p => p.which.includes('HEADER'))?.body || {};
|
|
545
|
+
return {
|
|
546
|
+
uid: msg.attributes.uid,
|
|
547
|
+
date: header.date?.[0],
|
|
548
|
+
from: header.from?.[0],
|
|
549
|
+
to: header.to?.[0],
|
|
550
|
+
subject: header.subject?.[0]
|
|
551
|
+
};
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
555
|
+
} finally {
|
|
556
|
+
connection.end();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
case 'list_drafts': {
|
|
561
|
+
const limit = args.limit || 20;
|
|
562
|
+
const connection = await connectIMAP();
|
|
563
|
+
|
|
564
|
+
try {
|
|
565
|
+
const draftsFolder = await findDraftsFolder(connection);
|
|
566
|
+
await connection.openBox(draftsFolder);
|
|
567
|
+
|
|
568
|
+
const fetchOptions = {
|
|
569
|
+
bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)'],
|
|
570
|
+
struct: true
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const messages = await connection.search(['ALL'], fetchOptions);
|
|
574
|
+
const results = messages.slice(-limit).reverse().map(msg => {
|
|
575
|
+
const header = msg.parts.find(p => p.which.includes('HEADER'))?.body || {};
|
|
576
|
+
return {
|
|
577
|
+
uid: msg.attributes.uid,
|
|
578
|
+
date: header.date?.[0],
|
|
579
|
+
to: header.to?.[0],
|
|
580
|
+
subject: header.subject?.[0]
|
|
581
|
+
};
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
return { content: [{ type: 'text', text: JSON.stringify({ folder: draftsFolder, drafts: results }, null, 2) }] };
|
|
585
|
+
} finally {
|
|
586
|
+
connection.end();
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
case 'get_draft': {
|
|
591
|
+
const connection = await connectIMAP();
|
|
592
|
+
|
|
593
|
+
try {
|
|
594
|
+
const draftsFolder = await findDraftsFolder(connection);
|
|
595
|
+
await connection.openBox(draftsFolder);
|
|
596
|
+
|
|
597
|
+
const fetchOptions = {
|
|
598
|
+
bodies: [''],
|
|
599
|
+
struct: true
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const messages = await connection.search([['UID', args.uid]], fetchOptions);
|
|
603
|
+
|
|
604
|
+
if (messages.length === 0) {
|
|
605
|
+
return { content: [{ type: 'text', text: 'Draft not found' }] };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const msg = messages[0];
|
|
609
|
+
const rawBody = msg.parts.find(p => p.which === '')?.body;
|
|
610
|
+
const parsed = await simpleParser(rawBody);
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
content: [{
|
|
614
|
+
type: 'text',
|
|
615
|
+
text: JSON.stringify({
|
|
616
|
+
uid: msg.attributes.uid,
|
|
617
|
+
to: parsed.to?.text,
|
|
618
|
+
cc: parsed.cc?.text,
|
|
619
|
+
bcc: parsed.bcc?.text,
|
|
620
|
+
subject: parsed.subject,
|
|
621
|
+
date: parsed.date,
|
|
622
|
+
text: parsed.text,
|
|
623
|
+
html: parsed.html
|
|
624
|
+
}, null, 2)
|
|
625
|
+
}]
|
|
626
|
+
};
|
|
627
|
+
} finally {
|
|
628
|
+
connection.end();
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
case 'create_draft': {
|
|
633
|
+
const connection = await connectIMAP();
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
const draftsFolder = await findDraftsFolder(connection);
|
|
637
|
+
|
|
638
|
+
// Build RFC 2822 compliant email message
|
|
639
|
+
const boundary = `----=_Part_${Date.now()}`;
|
|
640
|
+
let message = '';
|
|
641
|
+
message += `From: ${IMAP_CONFIG.imap.user}\r\n`;
|
|
642
|
+
message += `To: ${args.to}\r\n`;
|
|
643
|
+
if (args.cc) message += `Cc: ${args.cc}\r\n`;
|
|
644
|
+
if (args.bcc) message += `Bcc: ${args.bcc}\r\n`;
|
|
645
|
+
message += `Subject: ${args.subject}\r\n`;
|
|
646
|
+
message += `Date: ${new Date().toUTCString()}\r\n`;
|
|
647
|
+
message += `MIME-Version: 1.0\r\n`;
|
|
648
|
+
|
|
649
|
+
if (args.html) {
|
|
650
|
+
message += `Content-Type: multipart/alternative; boundary="${boundary}"\r\n\r\n`;
|
|
651
|
+
message += `--${boundary}\r\n`;
|
|
652
|
+
message += `Content-Type: text/plain; charset=utf-8\r\n\r\n`;
|
|
653
|
+
message += `${args.body || ''}\r\n`;
|
|
654
|
+
message += `--${boundary}\r\n`;
|
|
655
|
+
message += `Content-Type: text/html; charset=utf-8\r\n\r\n`;
|
|
656
|
+
message += `${args.html}\r\n`;
|
|
657
|
+
message += `--${boundary}--\r\n`;
|
|
658
|
+
} else {
|
|
659
|
+
message += `Content-Type: text/plain; charset=utf-8\r\n\r\n`;
|
|
660
|
+
message += `${args.body || ''}\r\n`;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
await connection.append(message, { mailbox: draftsFolder, flags: ['\\Draft'] });
|
|
664
|
+
|
|
665
|
+
return { content: [{ type: 'text', text: `Draft created successfully in ${draftsFolder}` }] };
|
|
666
|
+
} finally {
|
|
667
|
+
connection.end();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
case 'update_draft': {
|
|
672
|
+
const connection = await connectIMAP();
|
|
673
|
+
|
|
674
|
+
try {
|
|
675
|
+
const draftsFolder = await findDraftsFolder(connection);
|
|
676
|
+
await connection.openBox(draftsFolder);
|
|
677
|
+
|
|
678
|
+
// Delete old draft
|
|
679
|
+
await connection.addFlags(args.uid, ['\\Deleted']);
|
|
680
|
+
await connection.closeBox(true); // Expunge
|
|
681
|
+
|
|
682
|
+
// Create new draft
|
|
683
|
+
await connection.openBox(draftsFolder);
|
|
684
|
+
|
|
685
|
+
const boundary = `----=_Part_${Date.now()}`;
|
|
686
|
+
let message = '';
|
|
687
|
+
message += `From: ${IMAP_CONFIG.imap.user}\r\n`;
|
|
688
|
+
message += `To: ${args.to}\r\n`;
|
|
689
|
+
if (args.cc) message += `Cc: ${args.cc}\r\n`;
|
|
690
|
+
if (args.bcc) message += `Bcc: ${args.bcc}\r\n`;
|
|
691
|
+
message += `Subject: ${args.subject}\r\n`;
|
|
692
|
+
message += `Date: ${new Date().toUTCString()}\r\n`;
|
|
693
|
+
message += `MIME-Version: 1.0\r\n`;
|
|
694
|
+
|
|
695
|
+
if (args.html) {
|
|
696
|
+
message += `Content-Type: multipart/alternative; boundary="${boundary}"\r\n\r\n`;
|
|
697
|
+
message += `--${boundary}\r\n`;
|
|
698
|
+
message += `Content-Type: text/plain; charset=utf-8\r\n\r\n`;
|
|
699
|
+
message += `${args.body || ''}\r\n`;
|
|
700
|
+
message += `--${boundary}\r\n`;
|
|
701
|
+
message += `Content-Type: text/html; charset=utf-8\r\n\r\n`;
|
|
702
|
+
message += `${args.html}\r\n`;
|
|
703
|
+
message += `--${boundary}--\r\n`;
|
|
704
|
+
} else {
|
|
705
|
+
message += `Content-Type: text/plain; charset=utf-8\r\n\r\n`;
|
|
706
|
+
message += `${args.body || ''}\r\n`;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
await connection.append(message, { mailbox: draftsFolder, flags: ['\\Draft'] });
|
|
710
|
+
|
|
711
|
+
return { content: [{ type: 'text', text: 'Draft updated successfully' }] };
|
|
712
|
+
} finally {
|
|
713
|
+
connection.end();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
case 'send_email': {
|
|
718
|
+
if (!SMTP_CONFIG.host) {
|
|
719
|
+
return {
|
|
720
|
+
content: [{ type: 'text', text: 'Error: SMTP_HOST not configured. Cannot send emails.' }],
|
|
721
|
+
isError: true
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const transporter = nodemailer.createTransport(SMTP_CONFIG);
|
|
726
|
+
|
|
727
|
+
const mailOptions = {
|
|
728
|
+
from: SMTP_CONFIG.auth.user,
|
|
729
|
+
to: args.to,
|
|
730
|
+
subject: args.subject,
|
|
731
|
+
text: args.body,
|
|
732
|
+
html: args.html,
|
|
733
|
+
cc: args.cc,
|
|
734
|
+
bcc: args.bcc
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
const info = await transporter.sendMail(mailOptions);
|
|
738
|
+
|
|
739
|
+
return {
|
|
740
|
+
content: [{
|
|
741
|
+
type: 'text',
|
|
742
|
+
text: JSON.stringify({
|
|
743
|
+
success: true,
|
|
744
|
+
messageId: info.messageId,
|
|
745
|
+
response: info.response
|
|
746
|
+
}, null, 2)
|
|
747
|
+
}]
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
case 'download_attachment': {
|
|
752
|
+
const folder = args.folder || 'INBOX';
|
|
753
|
+
const connection = await connectIMAP();
|
|
754
|
+
|
|
755
|
+
try {
|
|
756
|
+
await connection.openBox(folder);
|
|
757
|
+
|
|
758
|
+
const messages = await connection.search([['UID', args.uid]], {
|
|
759
|
+
bodies: [''],
|
|
760
|
+
struct: true
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
if (messages.length === 0) {
|
|
764
|
+
return { content: [{ type: 'text', text: 'Email not found' }] };
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const rawBody = messages[0].parts.find(p => p.which === '')?.body;
|
|
768
|
+
const parsed = await simpleParser(rawBody);
|
|
769
|
+
|
|
770
|
+
if (!parsed.attachments || parsed.attachments.length === 0) {
|
|
771
|
+
return { content: [{ type: 'text', text: 'No attachments found in this email' }] };
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const idx = args.attachment_index || 0;
|
|
775
|
+
const attachment = parsed.attachments[idx];
|
|
776
|
+
|
|
777
|
+
if (!attachment) {
|
|
778
|
+
return { content: [{ type: 'text', text: `Attachment index ${idx} out of range. Total: ${parsed.attachments.length}` }] };
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const outputDir = args.output_dir || process.cwd();
|
|
782
|
+
const filename = args.filename || attachment.filename || `attachment_${idx}`;
|
|
783
|
+
const filePath = path.join(outputDir, filename);
|
|
784
|
+
|
|
785
|
+
fs.writeFileSync(filePath, attachment.content);
|
|
786
|
+
|
|
787
|
+
return {
|
|
788
|
+
content: [{
|
|
789
|
+
type: 'text',
|
|
790
|
+
text: JSON.stringify({
|
|
791
|
+
success: true,
|
|
792
|
+
filename: attachment.filename,
|
|
793
|
+
contentType: attachment.contentType,
|
|
794
|
+
size: attachment.size,
|
|
795
|
+
savedTo: filePath
|
|
796
|
+
}, null, 2)
|
|
797
|
+
}]
|
|
798
|
+
};
|
|
799
|
+
} finally {
|
|
800
|
+
connection.end();
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
case 'delete_email': {
|
|
805
|
+
const folder = args.folder || 'INBOX';
|
|
806
|
+
const connection = await connectIMAP();
|
|
807
|
+
|
|
808
|
+
try {
|
|
809
|
+
await connection.openBox(folder);
|
|
810
|
+
await connection.addFlags(args.uid, ['\\Deleted']);
|
|
811
|
+
await connection.closeBox(true); // Expunge
|
|
812
|
+
|
|
813
|
+
return { content: [{ type: 'text', text: 'Email deleted successfully' }] };
|
|
814
|
+
} finally {
|
|
815
|
+
connection.end();
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
default:
|
|
820
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }] };
|
|
821
|
+
}
|
|
822
|
+
} catch (error) {
|
|
823
|
+
return {
|
|
824
|
+
content: [{
|
|
825
|
+
type: 'text',
|
|
826
|
+
text: `Error: ${error.message}`
|
|
827
|
+
}],
|
|
828
|
+
isError: true
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// Start server
|
|
834
|
+
async function main() {
|
|
835
|
+
validateConfig();
|
|
836
|
+
const transport = new StdioServerTransport();
|
|
837
|
+
await server.connect(transport);
|
|
838
|
+
console.error('IMAP Email MCP Server running on stdio');
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@easyasstudio/imap-email-mcp",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "MCP server for Claude Code that provides email capabilities through IMAP/SMTP. Read, search, compose, and manage emails from any IMAP provider.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"imap-email-mcp": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"mcp-server",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"claude",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"anthropic",
|
|
20
|
+
"email",
|
|
21
|
+
"imap",
|
|
22
|
+
"smtp",
|
|
23
|
+
"mail",
|
|
24
|
+
"inbox",
|
|
25
|
+
"gmail",
|
|
26
|
+
"outlook",
|
|
27
|
+
"email-client",
|
|
28
|
+
"ai-tools",
|
|
29
|
+
"llm-tools"
|
|
30
|
+
],
|
|
31
|
+
"author": "easyasstudio",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/woshihoujinxin/imap-email-mcp.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/woshihoujinxin/imap-email-mcp/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/woshihoujinxin/imap-email-mcp#readme",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
46
|
+
"imap-simple": "^5.1.0",
|
|
47
|
+
"mailparser": "^3.7.2",
|
|
48
|
+
"nodemailer": "^6.9.16"
|
|
49
|
+
}
|
|
50
|
+
}
|