gmail-workspace-mcp-server 0.1.1 → 0.2.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/README.md +38 -12
- package/build/index.integration-with-mock.js +10 -8
- package/build/index.js +21 -7
- package/build/oauth-setup.d.ts +14 -0
- package/build/oauth-setup.js +135 -0
- package/package.json +1 -1
- package/shared/gmail-client/lib/drafts.d.ts +2 -1
- package/shared/gmail-client/lib/mime-utils.d.ts +5 -2
- package/shared/gmail-client/lib/mime-utils.js +21 -3
- package/shared/gmail-client/lib/send-message.d.ts +2 -1
- package/shared/server.d.ts +8 -4
- package/shared/tools/draft-email.d.ts +31 -6
- package/shared/tools/draft-email.js +31 -8
- package/shared/tools/send-email.d.ts +16 -7
- package/shared/tools/send-email.js +27 -11
package/README.md
CHANGED
|
@@ -48,24 +48,22 @@ Use this for personal `@gmail.com` accounts or any Google account without Worksp
|
|
|
48
48
|
|
|
49
49
|
#### Getting a Refresh Token
|
|
50
50
|
|
|
51
|
-
Run the
|
|
51
|
+
Run the built-in setup command:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
|
|
55
|
-
cd mcp-servers/experimental/gmail
|
|
56
|
-
npx tsx scripts/oauth-setup.ts <client_id> <client_secret>
|
|
54
|
+
npx gmail-workspace-mcp-server oauth-setup <client_id> <client_secret>
|
|
57
55
|
```
|
|
58
56
|
|
|
59
57
|
You can also pass credentials via environment variables:
|
|
60
58
|
|
|
61
59
|
```bash
|
|
62
|
-
GMAIL_OAUTH_CLIENT_ID=... GMAIL_OAUTH_CLIENT_SECRET=... npx
|
|
60
|
+
GMAIL_OAUTH_CLIENT_ID=... GMAIL_OAUTH_CLIENT_SECRET=... npx gmail-workspace-mcp-server oauth-setup
|
|
63
61
|
```
|
|
64
62
|
|
|
65
63
|
**Port conflict?** If port 3000 is already in use, specify a different port:
|
|
66
64
|
|
|
67
65
|
```bash
|
|
68
|
-
PORT=3001 npx
|
|
66
|
+
PORT=3001 npx gmail-workspace-mcp-server oauth-setup <client_id> <client_secret>
|
|
69
67
|
```
|
|
70
68
|
|
|
71
69
|
Desktop app credentials automatically allow `http://localhost` redirects on any port, so no additional Google Cloud Console configuration is needed.
|
|
@@ -264,17 +262,32 @@ Create a draft email, optionally as a reply to an existing conversation.
|
|
|
264
262
|
|
|
265
263
|
- `to` (string, required): Recipient email address
|
|
266
264
|
- `subject` (string, required): Email subject
|
|
267
|
-
- `
|
|
265
|
+
- `plaintext_body` (string): Plain text body content (at least one of plaintext_body or html_body required)
|
|
266
|
+
- `html_body` (string): HTML body content for rich text formatting (at least one of plaintext_body or html_body required)
|
|
267
|
+
- `cc` (string, optional): CC recipients
|
|
268
|
+
- `bcc` (string, optional): BCC recipients
|
|
268
269
|
- `thread_id` (string, optional): Thread ID for replies
|
|
269
270
|
- `reply_to_email_id` (string, optional): Email ID to reply to (sets References/In-Reply-To headers)
|
|
270
271
|
|
|
271
|
-
|
|
272
|
+
At least one of `plaintext_body` or `html_body` must be provided. If both are provided, a multipart email is sent with both versions.
|
|
273
|
+
|
|
274
|
+
**Example (plain text):**
|
|
275
|
+
|
|
276
|
+
```json
|
|
277
|
+
{
|
|
278
|
+
"to": "recipient@example.com",
|
|
279
|
+
"subject": "Meeting Follow-up",
|
|
280
|
+
"plaintext_body": "Thanks for the meeting today!"
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Example (HTML):**
|
|
272
285
|
|
|
273
286
|
```json
|
|
274
287
|
{
|
|
275
288
|
"to": "recipient@example.com",
|
|
276
289
|
"subject": "Meeting Follow-up",
|
|
277
|
-
"
|
|
290
|
+
"html_body": "<p>Thanks for the meeting today! Check out <a href=\"https://example.com/notes\">the notes</a>.</p>"
|
|
278
291
|
}
|
|
279
292
|
```
|
|
280
293
|
|
|
@@ -286,18 +299,31 @@ Send an email directly or from an existing draft.
|
|
|
286
299
|
|
|
287
300
|
- `to` (string, conditional): Recipient email (required unless sending from draft)
|
|
288
301
|
- `subject` (string, conditional): Email subject (required unless sending from draft)
|
|
289
|
-
- `
|
|
302
|
+
- `plaintext_body` (string): Plain text body content (at least one of plaintext_body or html_body required, unless sending a draft)
|
|
303
|
+
- `html_body` (string): HTML body content for rich text formatting (at least one of plaintext_body or html_body required, unless sending a draft)
|
|
304
|
+
- `cc` (string, optional): CC recipients
|
|
305
|
+
- `bcc` (string, optional): BCC recipients
|
|
290
306
|
- `from_draft_id` (string, optional): Send an existing draft by ID
|
|
291
307
|
- `thread_id` (string, optional): Thread ID for replies
|
|
292
308
|
- `reply_to_email_id` (string, optional): Email ID to reply to
|
|
293
309
|
|
|
294
|
-
**Example (
|
|
310
|
+
**Example (plain text email):**
|
|
311
|
+
|
|
312
|
+
```json
|
|
313
|
+
{
|
|
314
|
+
"to": "recipient@example.com",
|
|
315
|
+
"subject": "Hello",
|
|
316
|
+
"plaintext_body": "This is a test email."
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Example (HTML email):**
|
|
295
321
|
|
|
296
322
|
```json
|
|
297
323
|
{
|
|
298
324
|
"to": "recipient@example.com",
|
|
299
325
|
"subject": "Hello",
|
|
300
|
-
"
|
|
326
|
+
"html_body": "<p>Check out <a href=\"https://example.com\">our website</a> for more details.</p>"
|
|
301
327
|
}
|
|
302
328
|
```
|
|
303
329
|
|
|
@@ -165,17 +165,18 @@ function createMockClient() {
|
|
|
165
165
|
return { ...email, labelIds: labels };
|
|
166
166
|
},
|
|
167
167
|
async createDraft(options) {
|
|
168
|
+
const bodyContent = options.plaintextBody || options.htmlBody || '';
|
|
168
169
|
const draft = {
|
|
169
170
|
id: `draft_${draftIdCounter++}`,
|
|
170
171
|
message: {
|
|
171
172
|
id: `msg_${messageIdCounter++}`,
|
|
172
173
|
threadId: options.threadId || `thread_${messageIdCounter}`,
|
|
173
174
|
labelIds: ['DRAFT'],
|
|
174
|
-
snippet:
|
|
175
|
+
snippet: bodyContent.substring(0, 100),
|
|
175
176
|
historyId: '12347',
|
|
176
177
|
internalDate: String(Date.now()),
|
|
177
178
|
payload: {
|
|
178
|
-
mimeType: 'text/plain',
|
|
179
|
+
mimeType: options.htmlBody ? 'text/html' : 'text/plain',
|
|
179
180
|
headers: [
|
|
180
181
|
{ name: 'Subject', value: options.subject },
|
|
181
182
|
{ name: 'From', value: 'me@example.com' },
|
|
@@ -183,8 +184,8 @@ function createMockClient() {
|
|
|
183
184
|
{ name: 'Date', value: new Date().toISOString() },
|
|
184
185
|
],
|
|
185
186
|
body: {
|
|
186
|
-
size:
|
|
187
|
-
data: Buffer.from(
|
|
187
|
+
size: bodyContent.length,
|
|
188
|
+
data: Buffer.from(bodyContent).toString('base64url'),
|
|
188
189
|
},
|
|
189
190
|
},
|
|
190
191
|
},
|
|
@@ -221,15 +222,16 @@ function createMockClient() {
|
|
|
221
222
|
mockDrafts.splice(index, 1);
|
|
222
223
|
},
|
|
223
224
|
async sendMessage(options) {
|
|
225
|
+
const bodyContent = options.plaintextBody || options.htmlBody || '';
|
|
224
226
|
const sentMessage = {
|
|
225
227
|
id: `msg_${messageIdCounter++}`,
|
|
226
228
|
threadId: options.threadId || `thread_${messageIdCounter}`,
|
|
227
229
|
labelIds: ['SENT'],
|
|
228
|
-
snippet:
|
|
230
|
+
snippet: bodyContent.substring(0, 100),
|
|
229
231
|
historyId: '12348',
|
|
230
232
|
internalDate: String(Date.now()),
|
|
231
233
|
payload: {
|
|
232
|
-
mimeType: 'text/plain',
|
|
234
|
+
mimeType: options.htmlBody ? 'text/html' : 'text/plain',
|
|
233
235
|
headers: [
|
|
234
236
|
{ name: 'Subject', value: options.subject },
|
|
235
237
|
{ name: 'From', value: 'me@example.com' },
|
|
@@ -237,8 +239,8 @@ function createMockClient() {
|
|
|
237
239
|
{ name: 'Date', value: new Date().toISOString() },
|
|
238
240
|
],
|
|
239
241
|
body: {
|
|
240
|
-
size:
|
|
241
|
-
data: Buffer.from(
|
|
242
|
+
size: bodyContent.length,
|
|
243
|
+
data: Buffer.from(bodyContent).toString('base64url'),
|
|
242
244
|
},
|
|
243
245
|
},
|
|
244
246
|
};
|
package/build/index.js
CHANGED
|
@@ -5,12 +5,31 @@ import { dirname, join } from 'path';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { createMCPServer } from '../shared/index.js';
|
|
7
7
|
import { logServerStart, logError, logWarning } from '../shared/logging.js';
|
|
8
|
+
import { runOAuthSetup } from './oauth-setup.js';
|
|
8
9
|
// Read version from package.json
|
|
9
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const packageJsonPath = join(__dirname, '..', 'package.json');
|
|
11
12
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
12
13
|
const VERSION = packageJson.version;
|
|
13
14
|
// =============================================================================
|
|
15
|
+
// CLI SUBCOMMAND HANDLING
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Check for subcommands before env validation (e.g., "oauth-setup")
|
|
18
|
+
const subcommand = process.argv[2];
|
|
19
|
+
if (subcommand === 'oauth-setup') {
|
|
20
|
+
runOAuthSetup(process.argv.slice(3)).catch((error) => {
|
|
21
|
+
console.error('Error:', error.message);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// Run the MCP server (default behavior)
|
|
27
|
+
main().catch((error) => {
|
|
28
|
+
logError('main', error);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// =============================================================================
|
|
14
33
|
// ENVIRONMENT VALIDATION
|
|
15
34
|
// =============================================================================
|
|
16
35
|
function validateEnvironment() {
|
|
@@ -45,7 +64,7 @@ function validateEnvironment() {
|
|
|
45
64
|
console.error(' GMAIL_OAUTH_CLIENT_SECRET: OAuth2 client secret');
|
|
46
65
|
console.error(' GMAIL_OAUTH_REFRESH_TOKEN: Refresh token from one-time consent flow');
|
|
47
66
|
console.error('\nRun the setup script to obtain a refresh token:');
|
|
48
|
-
console.error(' npx
|
|
67
|
+
console.error(' npx gmail-workspace-mcp-server oauth-setup <client_id> <client_secret>');
|
|
49
68
|
console.error('\n======================================================\n');
|
|
50
69
|
process.exit(1);
|
|
51
70
|
}
|
|
@@ -83,7 +102,7 @@ function validateEnvironment() {
|
|
|
83
102
|
console.error(' GMAIL_OAUTH_CLIENT_ID: OAuth2 client ID from Google Cloud Console');
|
|
84
103
|
console.error(' GMAIL_OAUTH_CLIENT_SECRET: OAuth2 client secret');
|
|
85
104
|
console.error(' GMAIL_OAUTH_REFRESH_TOKEN: Refresh token from one-time consent flow');
|
|
86
|
-
console.error('\n Setup: Run `npx
|
|
105
|
+
console.error('\n Setup: Run `npx gmail-workspace-mcp-server oauth-setup <client_id> <client_secret>`');
|
|
87
106
|
console.error('\n--- Option 2: Service Account (for Google Workspace) ---');
|
|
88
107
|
console.error(' GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL: Service account email address');
|
|
89
108
|
console.error(' Example: my-service-account@my-project.iam.gserviceaccount.com');
|
|
@@ -123,8 +142,3 @@ async function main() {
|
|
|
123
142
|
await server.connect(transport);
|
|
124
143
|
logServerStart('Gmail');
|
|
125
144
|
}
|
|
126
|
-
// Run the server
|
|
127
|
-
main().catch((error) => {
|
|
128
|
-
logError('main', error);
|
|
129
|
-
process.exit(1);
|
|
130
|
-
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2 setup flow for obtaining a Gmail refresh token.
|
|
3
|
+
*
|
|
4
|
+
* This module is invoked as a CLI subcommand:
|
|
5
|
+
* npx gmail-workspace-mcp-server oauth-setup <client_id> <client_secret>
|
|
6
|
+
*
|
|
7
|
+
* It starts a local HTTP server, opens the Google OAuth consent flow,
|
|
8
|
+
* and prints the resulting refresh token for use in MCP server configuration.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Run the OAuth2 setup flow.
|
|
12
|
+
* @param args - CLI arguments after "oauth-setup" (i.e., [client_id, client_secret])
|
|
13
|
+
*/
|
|
14
|
+
export declare function runOAuthSetup(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2 setup flow for obtaining a Gmail refresh token.
|
|
3
|
+
*
|
|
4
|
+
* This module is invoked as a CLI subcommand:
|
|
5
|
+
* npx gmail-workspace-mcp-server oauth-setup <client_id> <client_secret>
|
|
6
|
+
*
|
|
7
|
+
* It starts a local HTTP server, opens the Google OAuth consent flow,
|
|
8
|
+
* and prints the resulting refresh token for use in MCP server configuration.
|
|
9
|
+
*/
|
|
10
|
+
import http from 'node:http';
|
|
11
|
+
import { OAuth2Client } from 'google-auth-library';
|
|
12
|
+
const CALLBACK_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
13
|
+
const SCOPES = [
|
|
14
|
+
'https://www.googleapis.com/auth/gmail.readonly',
|
|
15
|
+
'https://www.googleapis.com/auth/gmail.modify',
|
|
16
|
+
'https://www.googleapis.com/auth/gmail.compose',
|
|
17
|
+
'https://www.googleapis.com/auth/gmail.send',
|
|
18
|
+
];
|
|
19
|
+
function waitForCallback(port) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const server = http.createServer((req, res) => {
|
|
22
|
+
if (!req.url?.startsWith('/callback')) {
|
|
23
|
+
res.writeHead(404);
|
|
24
|
+
res.end('Not found');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
28
|
+
const code = url.searchParams.get('code');
|
|
29
|
+
const error = url.searchParams.get('error');
|
|
30
|
+
if (error) {
|
|
31
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
32
|
+
res.end('<html><body><h1>Authorization Failed</h1><p>You can close this window.</p></body></html>');
|
|
33
|
+
server.close();
|
|
34
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!code) {
|
|
38
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
39
|
+
res.end('<html><body><h1>Missing Code</h1><p>No authorization code received.</p></body></html>');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
43
|
+
res.end('<html><body><h1>Authorization Successful!</h1><p>You can close this window and return to the terminal.</p></body></html>');
|
|
44
|
+
clearTimeout(timeout);
|
|
45
|
+
server.close();
|
|
46
|
+
resolve(code);
|
|
47
|
+
});
|
|
48
|
+
const timeout = setTimeout(() => {
|
|
49
|
+
console.error('\nTimeout: No callback received within 5 minutes. Exiting.');
|
|
50
|
+
server.close();
|
|
51
|
+
reject(new Error('OAuth callback timeout - no response within 5 minutes'));
|
|
52
|
+
}, CALLBACK_TIMEOUT_MS);
|
|
53
|
+
server.listen(port, () => {
|
|
54
|
+
// Server is listening
|
|
55
|
+
});
|
|
56
|
+
server.on('error', (err) => {
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
if (err.code === 'EADDRINUSE') {
|
|
59
|
+
reject(new Error(`Port ${port} is already in use. Please free it or set PORT env var.`));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
reject(err);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Run the OAuth2 setup flow.
|
|
69
|
+
* @param args - CLI arguments after "oauth-setup" (i.e., [client_id, client_secret])
|
|
70
|
+
*/
|
|
71
|
+
export async function runOAuthSetup(args) {
|
|
72
|
+
const clientId = args[0] || process.env.GMAIL_OAUTH_CLIENT_ID;
|
|
73
|
+
const clientSecret = args[1] || process.env.GMAIL_OAUTH_CLIENT_SECRET;
|
|
74
|
+
if (!clientId || !clientSecret) {
|
|
75
|
+
console.error('Usage: npx gmail-workspace-mcp-server oauth-setup <client_id> <client_secret>');
|
|
76
|
+
console.error('');
|
|
77
|
+
console.error('Or set environment variables:');
|
|
78
|
+
console.error(' GMAIL_OAUTH_CLIENT_ID=... GMAIL_OAUTH_CLIENT_SECRET=... npx gmail-workspace-mcp-server oauth-setup');
|
|
79
|
+
console.error('');
|
|
80
|
+
console.error('Get your OAuth2 credentials from:');
|
|
81
|
+
console.error(' https://console.cloud.google.com/apis/credentials');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const port = parseInt(process.env.PORT || '3000', 10);
|
|
85
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
86
|
+
const oauth2Client = new OAuth2Client(clientId, clientSecret, redirectUri);
|
|
87
|
+
const authorizeUrl = oauth2Client.generateAuthUrl({
|
|
88
|
+
access_type: 'offline',
|
|
89
|
+
scope: SCOPES,
|
|
90
|
+
prompt: 'consent', // Force consent to ensure refresh token is returned
|
|
91
|
+
});
|
|
92
|
+
console.log('\n=== Gmail OAuth2 Setup ===\n');
|
|
93
|
+
console.log('1. Open this URL in your browser:\n');
|
|
94
|
+
console.log(` ${authorizeUrl}\n`);
|
|
95
|
+
console.log('2. Sign in and authorize the application');
|
|
96
|
+
console.log(`3. You will be redirected to localhost:${port}/callback\n`);
|
|
97
|
+
console.log('Waiting for callback (5 minute timeout)...\n');
|
|
98
|
+
const code = await waitForCallback(port);
|
|
99
|
+
console.log('Authorization code received! Exchanging for tokens...\n');
|
|
100
|
+
const { tokens } = await oauth2Client.getToken(code);
|
|
101
|
+
if (!tokens.refresh_token) {
|
|
102
|
+
console.error('ERROR: No refresh token received.');
|
|
103
|
+
console.error('');
|
|
104
|
+
console.error('This can happen if:');
|
|
105
|
+
console.error(' - You previously authorized this app (revoke access at https://myaccount.google.com/permissions)');
|
|
106
|
+
console.error(' - The OAuth consent screen is still in "Testing" mode');
|
|
107
|
+
console.error('');
|
|
108
|
+
console.error('Try revoking access and running this script again.');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
console.log('=== Setup Complete ===\n');
|
|
112
|
+
console.log('Add these environment variables to your MCP server configuration:\n');
|
|
113
|
+
console.log(` GMAIL_OAUTH_CLIENT_ID=${clientId}`);
|
|
114
|
+
console.log(` GMAIL_OAUTH_CLIENT_SECRET=${clientSecret}`);
|
|
115
|
+
console.log(` GMAIL_OAUTH_REFRESH_TOKEN=${tokens.refresh_token}`);
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log('SECURITY NOTE: Keep your refresh token secure. Anyone with this token and your');
|
|
118
|
+
console.log('client credentials can access your Gmail account.\n');
|
|
119
|
+
console.log('Example Claude Desktop config:\n');
|
|
120
|
+
console.log(JSON.stringify({
|
|
121
|
+
mcpServers: {
|
|
122
|
+
gmail: {
|
|
123
|
+
command: 'npx',
|
|
124
|
+
args: ['gmail-workspace-mcp-server'],
|
|
125
|
+
env: {
|
|
126
|
+
GMAIL_OAUTH_CLIENT_ID: clientId,
|
|
127
|
+
GMAIL_OAUTH_CLIENT_SECRET: clientSecret,
|
|
128
|
+
GMAIL_OAUTH_REFRESH_TOKEN: tokens.refresh_token,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
}, null, 2));
|
|
133
|
+
console.log('');
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
package/package.json
CHANGED
|
@@ -13,7 +13,8 @@ interface DraftListItem {
|
|
|
13
13
|
export declare function createDraft(baseUrl: string, headers: Record<string, string>, from: string, options: {
|
|
14
14
|
to: string;
|
|
15
15
|
subject: string;
|
|
16
|
-
|
|
16
|
+
plaintextBody?: string;
|
|
17
|
+
htmlBody?: string;
|
|
17
18
|
cc?: string;
|
|
18
19
|
bcc?: string;
|
|
19
20
|
threadId?: string;
|
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
export interface MimeMessageOptions {
|
|
5
5
|
to: string;
|
|
6
6
|
subject: string;
|
|
7
|
-
|
|
7
|
+
plaintextBody?: string;
|
|
8
|
+
htmlBody?: string;
|
|
8
9
|
cc?: string;
|
|
9
10
|
bcc?: string;
|
|
10
11
|
inReplyTo?: string;
|
|
11
12
|
references?: string;
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
|
-
* Builds a MIME message from email options
|
|
15
|
+
* Builds a MIME message from email options.
|
|
16
|
+
* If both plaintextBody and htmlBody are provided, creates a multipart/alternative message.
|
|
17
|
+
* If only one is provided, creates a single-part message with the appropriate content type.
|
|
15
18
|
*/
|
|
16
19
|
export declare function buildMimeMessage(from: string, options: MimeMessageOptions): string;
|
|
17
20
|
/**
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* MIME message utilities for building and encoding email messages
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* Builds a MIME message from email options
|
|
5
|
+
* Builds a MIME message from email options.
|
|
6
|
+
* If both plaintextBody and htmlBody are provided, creates a multipart/alternative message.
|
|
7
|
+
* If only one is provided, creates a single-part message with the appropriate content type.
|
|
6
8
|
*/
|
|
7
9
|
export function buildMimeMessage(from, options) {
|
|
8
10
|
const headers = [
|
|
@@ -10,7 +12,6 @@ export function buildMimeMessage(from, options) {
|
|
|
10
12
|
`To: ${options.to}`,
|
|
11
13
|
`Subject: ${options.subject}`,
|
|
12
14
|
'MIME-Version: 1.0',
|
|
13
|
-
'Content-Type: text/plain; charset=utf-8',
|
|
14
15
|
];
|
|
15
16
|
if (options.cc) {
|
|
16
17
|
headers.push(`Cc: ${options.cc}`);
|
|
@@ -24,7 +25,24 @@ export function buildMimeMessage(from, options) {
|
|
|
24
25
|
if (options.references) {
|
|
25
26
|
headers.push(`References: ${options.references}`);
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
+
// If both plain text and HTML are provided, use multipart/alternative
|
|
29
|
+
if (options.plaintextBody && options.htmlBody) {
|
|
30
|
+
const boundary = `boundary_${Date.now()}_${Math.random().toString(36).substring(2)}`;
|
|
31
|
+
headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
|
|
32
|
+
const parts = [
|
|
33
|
+
`--${boundary}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${options.plaintextBody}`,
|
|
34
|
+
`--${boundary}\r\nContent-Type: text/html; charset=utf-8\r\n\r\n${options.htmlBody}`,
|
|
35
|
+
`--${boundary}--`,
|
|
36
|
+
];
|
|
37
|
+
return headers.join('\r\n') + '\r\n\r\n' + parts.join('\r\n');
|
|
38
|
+
}
|
|
39
|
+
// Single content type
|
|
40
|
+
if (options.htmlBody) {
|
|
41
|
+
headers.push('Content-Type: text/html; charset=utf-8');
|
|
42
|
+
return headers.join('\r\n') + '\r\n\r\n' + options.htmlBody;
|
|
43
|
+
}
|
|
44
|
+
headers.push('Content-Type: text/plain; charset=utf-8');
|
|
45
|
+
return headers.join('\r\n') + '\r\n\r\n' + (options.plaintextBody ?? '');
|
|
28
46
|
}
|
|
29
47
|
/**
|
|
30
48
|
* Converts a string to base64url encoding (RFC 4648)
|
|
@@ -5,7 +5,8 @@ import type { Email } from '../../types.js';
|
|
|
5
5
|
export declare function sendMessage(baseUrl: string, headers: Record<string, string>, from: string, options: {
|
|
6
6
|
to: string;
|
|
7
7
|
subject: string;
|
|
8
|
-
|
|
8
|
+
plaintextBody?: string;
|
|
9
|
+
htmlBody?: string;
|
|
9
10
|
cc?: string;
|
|
10
11
|
bcc?: string;
|
|
11
12
|
threadId?: string;
|
package/shared/server.d.ts
CHANGED
|
@@ -50,7 +50,8 @@ export interface IGmailClient {
|
|
|
50
50
|
createDraft(options: {
|
|
51
51
|
to: string;
|
|
52
52
|
subject: string;
|
|
53
|
-
|
|
53
|
+
plaintextBody?: string;
|
|
54
|
+
htmlBody?: string;
|
|
54
55
|
cc?: string;
|
|
55
56
|
bcc?: string;
|
|
56
57
|
threadId?: string;
|
|
@@ -85,7 +86,8 @@ export interface IGmailClient {
|
|
|
85
86
|
sendMessage(options: {
|
|
86
87
|
to: string;
|
|
87
88
|
subject: string;
|
|
88
|
-
|
|
89
|
+
plaintextBody?: string;
|
|
90
|
+
htmlBody?: string;
|
|
89
91
|
cc?: string;
|
|
90
92
|
bcc?: string;
|
|
91
93
|
threadId?: string;
|
|
@@ -167,7 +169,8 @@ declare abstract class BaseGmailClient implements IGmailClient {
|
|
|
167
169
|
createDraft(options: {
|
|
168
170
|
to: string;
|
|
169
171
|
subject: string;
|
|
170
|
-
|
|
172
|
+
plaintextBody?: string;
|
|
173
|
+
htmlBody?: string;
|
|
171
174
|
cc?: string;
|
|
172
175
|
bcc?: string;
|
|
173
176
|
threadId?: string;
|
|
@@ -190,7 +193,8 @@ declare abstract class BaseGmailClient implements IGmailClient {
|
|
|
190
193
|
sendMessage(options: {
|
|
191
194
|
to: string;
|
|
192
195
|
subject: string;
|
|
193
|
-
|
|
196
|
+
plaintextBody?: string;
|
|
197
|
+
htmlBody?: string;
|
|
194
198
|
cc?: string;
|
|
195
199
|
bcc?: string;
|
|
196
200
|
threadId?: string;
|
|
@@ -1,26 +1,47 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type { ClientFactory } from '../server.js';
|
|
4
|
-
export declare const DraftEmailSchema: z.ZodObject<{
|
|
4
|
+
export declare const DraftEmailSchema: z.ZodEffects<z.ZodObject<{
|
|
5
5
|
to: z.ZodString;
|
|
6
6
|
subject: z.ZodString;
|
|
7
|
-
|
|
7
|
+
plaintext_body: z.ZodOptional<z.ZodString>;
|
|
8
|
+
html_body: z.ZodOptional<z.ZodString>;
|
|
8
9
|
cc: z.ZodOptional<z.ZodString>;
|
|
9
10
|
bcc: z.ZodOptional<z.ZodString>;
|
|
10
11
|
thread_id: z.ZodOptional<z.ZodString>;
|
|
11
12
|
reply_to_email_id: z.ZodOptional<z.ZodString>;
|
|
12
13
|
}, "strip", z.ZodTypeAny, {
|
|
13
|
-
body: string;
|
|
14
14
|
to: string;
|
|
15
15
|
subject: string;
|
|
16
|
+
plaintext_body?: string | undefined;
|
|
17
|
+
html_body?: string | undefined;
|
|
16
18
|
cc?: string | undefined;
|
|
17
19
|
bcc?: string | undefined;
|
|
18
20
|
thread_id?: string | undefined;
|
|
19
21
|
reply_to_email_id?: string | undefined;
|
|
20
22
|
}, {
|
|
21
|
-
body: string;
|
|
22
23
|
to: string;
|
|
23
24
|
subject: string;
|
|
25
|
+
plaintext_body?: string | undefined;
|
|
26
|
+
html_body?: string | undefined;
|
|
27
|
+
cc?: string | undefined;
|
|
28
|
+
bcc?: string | undefined;
|
|
29
|
+
thread_id?: string | undefined;
|
|
30
|
+
reply_to_email_id?: string | undefined;
|
|
31
|
+
}>, {
|
|
32
|
+
to: string;
|
|
33
|
+
subject: string;
|
|
34
|
+
plaintext_body?: string | undefined;
|
|
35
|
+
html_body?: string | undefined;
|
|
36
|
+
cc?: string | undefined;
|
|
37
|
+
bcc?: string | undefined;
|
|
38
|
+
thread_id?: string | undefined;
|
|
39
|
+
reply_to_email_id?: string | undefined;
|
|
40
|
+
}, {
|
|
41
|
+
to: string;
|
|
42
|
+
subject: string;
|
|
43
|
+
plaintext_body?: string | undefined;
|
|
44
|
+
html_body?: string | undefined;
|
|
24
45
|
cc?: string | undefined;
|
|
25
46
|
bcc?: string | undefined;
|
|
26
47
|
thread_id?: string | undefined;
|
|
@@ -40,9 +61,13 @@ export declare function draftEmailTool(server: Server, clientFactory: ClientFact
|
|
|
40
61
|
type: string;
|
|
41
62
|
description: "Subject line of the email.";
|
|
42
63
|
};
|
|
43
|
-
|
|
64
|
+
plaintext_body: {
|
|
65
|
+
type: string;
|
|
66
|
+
description: "Plain text body content of the email. At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both versions.";
|
|
67
|
+
};
|
|
68
|
+
html_body: {
|
|
44
69
|
type: string;
|
|
45
|
-
description: "
|
|
70
|
+
description: "HTML body content of the email for rich text formatting (links, bold, lists, etc.). At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both versions.";
|
|
46
71
|
};
|
|
47
72
|
cc: {
|
|
48
73
|
type: string;
|
|
@@ -3,7 +3,8 @@ import { getHeader } from '../utils/email-helpers.js';
|
|
|
3
3
|
const PARAM_DESCRIPTIONS = {
|
|
4
4
|
to: 'Recipient email address(es). For multiple recipients, separate with commas.',
|
|
5
5
|
subject: 'Subject line of the email.',
|
|
6
|
-
|
|
6
|
+
plaintext_body: 'Plain text body content of the email. At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both versions.',
|
|
7
|
+
html_body: 'HTML body content of the email for rich text formatting (links, bold, lists, etc.). At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both versions.',
|
|
7
8
|
cc: 'CC recipient email address(es). For multiple, separate with commas.',
|
|
8
9
|
bcc: 'BCC recipient email address(es). For multiple, separate with commas.',
|
|
9
10
|
thread_id: 'Thread ID to add this draft to an existing conversation. ' +
|
|
@@ -11,26 +12,37 @@ const PARAM_DESCRIPTIONS = {
|
|
|
11
12
|
reply_to_email_id: 'Email ID to reply to. If provided, the draft will be formatted as a reply ' +
|
|
12
13
|
'with proper In-Reply-To and References headers. Also requires thread_id.',
|
|
13
14
|
};
|
|
14
|
-
export const DraftEmailSchema = z
|
|
15
|
+
export const DraftEmailSchema = z
|
|
16
|
+
.object({
|
|
15
17
|
to: z.string().min(1).describe(PARAM_DESCRIPTIONS.to),
|
|
16
18
|
subject: z.string().min(1).describe(PARAM_DESCRIPTIONS.subject),
|
|
17
|
-
|
|
19
|
+
plaintext_body: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.plaintext_body),
|
|
20
|
+
html_body: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.html_body),
|
|
18
21
|
cc: z.string().optional().describe(PARAM_DESCRIPTIONS.cc),
|
|
19
22
|
bcc: z.string().optional().describe(PARAM_DESCRIPTIONS.bcc),
|
|
20
23
|
thread_id: z.string().optional().describe(PARAM_DESCRIPTIONS.thread_id),
|
|
21
24
|
reply_to_email_id: z.string().optional().describe(PARAM_DESCRIPTIONS.reply_to_email_id),
|
|
25
|
+
})
|
|
26
|
+
.refine((data) => {
|
|
27
|
+
return Boolean(data.plaintext_body) || Boolean(data.html_body);
|
|
28
|
+
}, {
|
|
29
|
+
message: 'At least one of plaintext_body or html_body must be provided.',
|
|
22
30
|
});
|
|
23
31
|
const TOOL_DESCRIPTION = `Create a draft email that can be reviewed and sent later.
|
|
24
32
|
|
|
25
33
|
**Parameters:**
|
|
26
34
|
- to: Recipient email address(es) (required)
|
|
27
35
|
- subject: Email subject line (required)
|
|
28
|
-
-
|
|
36
|
+
- plaintext_body: Plain text body content (at least one of plaintext_body or html_body required)
|
|
37
|
+
- html_body: HTML body content for rich text formatting (at least one of plaintext_body or html_body required)
|
|
29
38
|
- cc: CC recipients (optional)
|
|
30
39
|
- bcc: BCC recipients (optional)
|
|
31
40
|
- thread_id: Thread ID to reply to an existing conversation (optional)
|
|
32
41
|
- reply_to_email_id: Email ID to reply to, sets proper reply headers (optional)
|
|
33
42
|
|
|
43
|
+
**Body content:**
|
|
44
|
+
At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both plain text and HTML versions. Use html_body for rich formatting like hyperlinks, bold text, or lists.
|
|
45
|
+
|
|
34
46
|
**Creating a reply:**
|
|
35
47
|
To create a draft reply to an existing email:
|
|
36
48
|
1. Get the thread_id and email_id from get_email_conversation
|
|
@@ -57,9 +69,13 @@ export function draftEmailTool(server, clientFactory) {
|
|
|
57
69
|
type: 'string',
|
|
58
70
|
description: PARAM_DESCRIPTIONS.subject,
|
|
59
71
|
},
|
|
60
|
-
|
|
72
|
+
plaintext_body: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: PARAM_DESCRIPTIONS.plaintext_body,
|
|
75
|
+
},
|
|
76
|
+
html_body: {
|
|
61
77
|
type: 'string',
|
|
62
|
-
description: PARAM_DESCRIPTIONS.
|
|
78
|
+
description: PARAM_DESCRIPTIONS.html_body,
|
|
63
79
|
},
|
|
64
80
|
cc: {
|
|
65
81
|
type: 'string',
|
|
@@ -78,7 +94,7 @@ export function draftEmailTool(server, clientFactory) {
|
|
|
78
94
|
description: PARAM_DESCRIPTIONS.reply_to_email_id,
|
|
79
95
|
},
|
|
80
96
|
},
|
|
81
|
-
required: ['to', 'subject'
|
|
97
|
+
required: ['to', 'subject'],
|
|
82
98
|
},
|
|
83
99
|
handler: async (args) => {
|
|
84
100
|
try {
|
|
@@ -103,7 +119,8 @@ export function draftEmailTool(server, clientFactory) {
|
|
|
103
119
|
const draft = await client.createDraft({
|
|
104
120
|
to: parsed.to,
|
|
105
121
|
subject: parsed.subject,
|
|
106
|
-
|
|
122
|
+
plaintextBody: parsed.plaintext_body,
|
|
123
|
+
htmlBody: parsed.html_body,
|
|
107
124
|
cc: parsed.cc,
|
|
108
125
|
bcc: parsed.bcc,
|
|
109
126
|
threadId: parsed.thread_id,
|
|
@@ -117,6 +134,12 @@ export function draftEmailTool(server, clientFactory) {
|
|
|
117
134
|
}
|
|
118
135
|
responseText += `\n\n**To:** ${parsed.to}`;
|
|
119
136
|
responseText += `\n**Subject:** ${parsed.subject}`;
|
|
137
|
+
const format = parsed.plaintext_body && parsed.html_body
|
|
138
|
+
? 'Multipart (plain text + HTML)'
|
|
139
|
+
: parsed.html_body
|
|
140
|
+
? 'HTML'
|
|
141
|
+
: 'Plain text';
|
|
142
|
+
responseText += `\n**Format:** ${format}`;
|
|
120
143
|
if (parsed.cc) {
|
|
121
144
|
responseText += `\n**CC:** ${parsed.cc}`;
|
|
122
145
|
}
|
|
@@ -4,43 +4,48 @@ import type { ClientFactory } from '../server.js';
|
|
|
4
4
|
export declare const SendEmailSchema: z.ZodEffects<z.ZodObject<{
|
|
5
5
|
to: z.ZodOptional<z.ZodString>;
|
|
6
6
|
subject: z.ZodOptional<z.ZodString>;
|
|
7
|
-
|
|
7
|
+
plaintext_body: z.ZodOptional<z.ZodString>;
|
|
8
|
+
html_body: z.ZodOptional<z.ZodString>;
|
|
8
9
|
cc: z.ZodOptional<z.ZodString>;
|
|
9
10
|
bcc: z.ZodOptional<z.ZodString>;
|
|
10
11
|
thread_id: z.ZodOptional<z.ZodString>;
|
|
11
12
|
reply_to_email_id: z.ZodOptional<z.ZodString>;
|
|
12
13
|
from_draft_id: z.ZodOptional<z.ZodString>;
|
|
13
14
|
}, "strip", z.ZodTypeAny, {
|
|
14
|
-
body?: string | undefined;
|
|
15
15
|
to?: string | undefined;
|
|
16
16
|
subject?: string | undefined;
|
|
17
|
+
plaintext_body?: string | undefined;
|
|
18
|
+
html_body?: string | undefined;
|
|
17
19
|
cc?: string | undefined;
|
|
18
20
|
bcc?: string | undefined;
|
|
19
21
|
thread_id?: string | undefined;
|
|
20
22
|
reply_to_email_id?: string | undefined;
|
|
21
23
|
from_draft_id?: string | undefined;
|
|
22
24
|
}, {
|
|
23
|
-
body?: string | undefined;
|
|
24
25
|
to?: string | undefined;
|
|
25
26
|
subject?: string | undefined;
|
|
27
|
+
plaintext_body?: string | undefined;
|
|
28
|
+
html_body?: string | undefined;
|
|
26
29
|
cc?: string | undefined;
|
|
27
30
|
bcc?: string | undefined;
|
|
28
31
|
thread_id?: string | undefined;
|
|
29
32
|
reply_to_email_id?: string | undefined;
|
|
30
33
|
from_draft_id?: string | undefined;
|
|
31
34
|
}>, {
|
|
32
|
-
body?: string | undefined;
|
|
33
35
|
to?: string | undefined;
|
|
34
36
|
subject?: string | undefined;
|
|
37
|
+
plaintext_body?: string | undefined;
|
|
38
|
+
html_body?: string | undefined;
|
|
35
39
|
cc?: string | undefined;
|
|
36
40
|
bcc?: string | undefined;
|
|
37
41
|
thread_id?: string | undefined;
|
|
38
42
|
reply_to_email_id?: string | undefined;
|
|
39
43
|
from_draft_id?: string | undefined;
|
|
40
44
|
}, {
|
|
41
|
-
body?: string | undefined;
|
|
42
45
|
to?: string | undefined;
|
|
43
46
|
subject?: string | undefined;
|
|
47
|
+
plaintext_body?: string | undefined;
|
|
48
|
+
html_body?: string | undefined;
|
|
44
49
|
cc?: string | undefined;
|
|
45
50
|
bcc?: string | undefined;
|
|
46
51
|
thread_id?: string | undefined;
|
|
@@ -61,9 +66,13 @@ export declare function sendEmailTool(server: Server, clientFactory: ClientFacto
|
|
|
61
66
|
type: string;
|
|
62
67
|
description: "Subject line of the email.";
|
|
63
68
|
};
|
|
64
|
-
|
|
69
|
+
plaintext_body: {
|
|
65
70
|
type: string;
|
|
66
|
-
description: "Plain text body content of the email.";
|
|
71
|
+
description: "Plain text body content of the email. At least one of plaintext_body or html_body must be provided (unless sending a draft). If both are provided, a multipart email is sent with both versions.";
|
|
72
|
+
};
|
|
73
|
+
html_body: {
|
|
74
|
+
type: string;
|
|
75
|
+
description: "HTML body content of the email for rich text formatting (links, bold, lists, etc.). At least one of plaintext_body or html_body must be provided (unless sending a draft). If both are provided, a multipart email is sent with both versions.";
|
|
67
76
|
};
|
|
68
77
|
cc: {
|
|
69
78
|
type: string;
|
|
@@ -3,7 +3,8 @@ import { getHeader } from '../utils/email-helpers.js';
|
|
|
3
3
|
const PARAM_DESCRIPTIONS = {
|
|
4
4
|
to: 'Recipient email address(es). For multiple recipients, separate with commas.',
|
|
5
5
|
subject: 'Subject line of the email.',
|
|
6
|
-
|
|
6
|
+
plaintext_body: 'Plain text body content of the email. At least one of plaintext_body or html_body must be provided (unless sending a draft). If both are provided, a multipart email is sent with both versions.',
|
|
7
|
+
html_body: 'HTML body content of the email for rich text formatting (links, bold, lists, etc.). At least one of plaintext_body or html_body must be provided (unless sending a draft). If both are provided, a multipart email is sent with both versions.',
|
|
7
8
|
cc: 'CC recipient email address(es). For multiple, separate with commas.',
|
|
8
9
|
bcc: 'BCC recipient email address(es). For multiple, separate with commas.',
|
|
9
10
|
thread_id: 'Thread ID to add this email to an existing conversation. ' +
|
|
@@ -11,13 +12,14 @@ const PARAM_DESCRIPTIONS = {
|
|
|
11
12
|
reply_to_email_id: 'Email ID to reply to. If provided, the email will be formatted as a reply ' +
|
|
12
13
|
'with proper In-Reply-To and References headers. Also requires thread_id.',
|
|
13
14
|
from_draft_id: 'Draft ID to send. If provided, sends the specified draft instead of composing a new email. ' +
|
|
14
|
-
'When using this, other parameters (to, subject,
|
|
15
|
+
'When using this, other parameters (to, subject, plaintext_body, etc.) are ignored.',
|
|
15
16
|
};
|
|
16
17
|
export const SendEmailSchema = z
|
|
17
18
|
.object({
|
|
18
19
|
to: z.string().optional().describe(PARAM_DESCRIPTIONS.to),
|
|
19
20
|
subject: z.string().optional().describe(PARAM_DESCRIPTIONS.subject),
|
|
20
|
-
|
|
21
|
+
plaintext_body: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.plaintext_body),
|
|
22
|
+
html_body: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.html_body),
|
|
21
23
|
cc: z.string().optional().describe(PARAM_DESCRIPTIONS.cc),
|
|
22
24
|
bcc: z.string().optional().describe(PARAM_DESCRIPTIONS.bcc),
|
|
23
25
|
thread_id: z.string().optional().describe(PARAM_DESCRIPTIONS.thread_id),
|
|
@@ -25,20 +27,21 @@ export const SendEmailSchema = z
|
|
|
25
27
|
from_draft_id: z.string().optional().describe(PARAM_DESCRIPTIONS.from_draft_id),
|
|
26
28
|
})
|
|
27
29
|
.refine((data) => {
|
|
28
|
-
// Either from_draft_id is provided, OR to, subject, and
|
|
30
|
+
// Either from_draft_id is provided, OR to, subject, and one of plaintext_body/html_body are all provided
|
|
29
31
|
if (data.from_draft_id) {
|
|
30
32
|
return true;
|
|
31
33
|
}
|
|
32
|
-
return data.to && data.subject && data.
|
|
34
|
+
return data.to && data.subject && (data.plaintext_body || data.html_body);
|
|
33
35
|
}, {
|
|
34
|
-
message: 'Either provide from_draft_id to send a draft, or provide to, subject, and
|
|
36
|
+
message: 'Either provide from_draft_id to send a draft, or provide to, subject, and at least one of plaintext_body or html_body to send a new email.',
|
|
35
37
|
});
|
|
36
38
|
const TOOL_DESCRIPTION = `Send an email immediately or send a previously created draft.
|
|
37
39
|
|
|
38
40
|
**Option 1: Send a new email**
|
|
39
41
|
- to: Recipient email address(es) (required)
|
|
40
42
|
- subject: Email subject line (required)
|
|
41
|
-
-
|
|
43
|
+
- plaintext_body: Plain text body content (at least one of plaintext_body or html_body required)
|
|
44
|
+
- html_body: HTML body content for rich text formatting (at least one of plaintext_body or html_body required)
|
|
42
45
|
- cc: CC recipients (optional)
|
|
43
46
|
- bcc: BCC recipients (optional)
|
|
44
47
|
- thread_id: Thread ID to reply to an existing conversation (optional)
|
|
@@ -47,6 +50,9 @@ const TOOL_DESCRIPTION = `Send an email immediately or send a previously created
|
|
|
47
50
|
**Option 2: Send a draft**
|
|
48
51
|
- from_draft_id: ID of the draft to send (all other parameters are ignored)
|
|
49
52
|
|
|
53
|
+
**Body content:**
|
|
54
|
+
At least one of plaintext_body or html_body must be provided. If both are provided, a multipart email is sent with both plain text and HTML versions. Use html_body for rich formatting like hyperlinks, bold text, or lists.
|
|
55
|
+
|
|
50
56
|
**Sending a reply:**
|
|
51
57
|
To send a reply to an existing email:
|
|
52
58
|
1. Get the thread_id and email_id from get_email_conversation
|
|
@@ -73,9 +79,13 @@ export function sendEmailTool(server, clientFactory) {
|
|
|
73
79
|
type: 'string',
|
|
74
80
|
description: PARAM_DESCRIPTIONS.subject,
|
|
75
81
|
},
|
|
76
|
-
|
|
82
|
+
plaintext_body: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: PARAM_DESCRIPTIONS.plaintext_body,
|
|
85
|
+
},
|
|
86
|
+
html_body: {
|
|
77
87
|
type: 'string',
|
|
78
|
-
description: PARAM_DESCRIPTIONS.
|
|
88
|
+
description: PARAM_DESCRIPTIONS.html_body,
|
|
79
89
|
},
|
|
80
90
|
cc: {
|
|
81
91
|
type: 'string',
|
|
@@ -120,7 +130,6 @@ export function sendEmailTool(server, clientFactory) {
|
|
|
120
130
|
// TypeScript knows these are defined due to the refine check
|
|
121
131
|
const to = parsed.to;
|
|
122
132
|
const subject = parsed.subject;
|
|
123
|
-
const body = parsed.body;
|
|
124
133
|
let inReplyTo;
|
|
125
134
|
let references;
|
|
126
135
|
// If replying to an email, get the Message-ID for proper threading
|
|
@@ -140,7 +149,8 @@ export function sendEmailTool(server, clientFactory) {
|
|
|
140
149
|
const sentEmail = await client.sendMessage({
|
|
141
150
|
to,
|
|
142
151
|
subject,
|
|
143
|
-
|
|
152
|
+
plaintextBody: parsed.plaintext_body,
|
|
153
|
+
htmlBody: parsed.html_body,
|
|
144
154
|
cc: parsed.cc,
|
|
145
155
|
bcc: parsed.bcc,
|
|
146
156
|
threadId: parsed.thread_id,
|
|
@@ -153,6 +163,12 @@ export function sendEmailTool(server, clientFactory) {
|
|
|
153
163
|
}
|
|
154
164
|
responseText += `\n\n**To:** ${to}`;
|
|
155
165
|
responseText += `\n**Subject:** ${subject}`;
|
|
166
|
+
const format = parsed.plaintext_body && parsed.html_body
|
|
167
|
+
? 'Multipart (plain text + HTML)'
|
|
168
|
+
: parsed.html_body
|
|
169
|
+
? 'HTML'
|
|
170
|
+
: 'Plain text';
|
|
171
|
+
responseText += `\n**Format:** ${format}`;
|
|
156
172
|
if (parsed.cc) {
|
|
157
173
|
responseText += `\n**CC:** ${parsed.cc}`;
|
|
158
174
|
}
|