gmail-workspace-mcp-server 0.4.3 → 0.4.5
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 +17 -7
- package/node_modules/@pulsemcp/mcp-elicitation/build/elicitation.js +22 -0
- package/package.json +1 -1
- package/shared/tools/draft-email.d.ts +20 -11
- package/shared/tools/draft-email.js +59 -17
- package/shared/tools/send-email.d.ts +2 -2
- package/shared/tools/send-email.js +16 -14
package/README.md
CHANGED
|
@@ -273,21 +273,22 @@ List draft emails from Gmail with optional thread filtering.
|
|
|
273
273
|
|
|
274
274
|
### upsert_draft_email
|
|
275
275
|
|
|
276
|
-
Create
|
|
276
|
+
Create, update, or delete a draft email. Optionally as a reply to an existing conversation.
|
|
277
277
|
|
|
278
278
|
**Parameters:**
|
|
279
279
|
|
|
280
|
-
- `draft_id` (string, optional): ID of an existing draft to update (omit to create a new draft)
|
|
281
|
-
- `
|
|
282
|
-
- `
|
|
283
|
-
- `
|
|
284
|
-
- `
|
|
280
|
+
- `draft_id` (string, optional): ID of an existing draft to update or delete (omit to create a new draft)
|
|
281
|
+
- `delete` (boolean, optional): Set to `true` to delete the draft specified by `draft_id`. All other parameters are ignored when deleting
|
|
282
|
+
- `to` (string, required for create/update): Recipient email address
|
|
283
|
+
- `subject` (string, required for create/update): Email subject
|
|
284
|
+
- `plaintext_body` (string): Plain text body content (at least one of plaintext_body or html_body required for create/update)
|
|
285
|
+
- `html_body` (string): HTML body content for rich text formatting (at least one of plaintext_body or html_body required for create/update)
|
|
285
286
|
- `cc` (string, optional): CC recipients
|
|
286
287
|
- `bcc` (string, optional): BCC recipients
|
|
287
288
|
- `thread_id` (string, optional): Thread ID for replies
|
|
288
289
|
- `reply_to_email_id` (string, optional): Email ID to reply to (sets References/In-Reply-To headers)
|
|
289
290
|
|
|
290
|
-
|
|
291
|
+
For create/update: at least one of `plaintext_body` or `html_body` must be provided. If both are provided, a multipart email is sent with both versions.
|
|
291
292
|
|
|
292
293
|
**Example (create new draft):**
|
|
293
294
|
|
|
@@ -310,6 +311,15 @@ At least one of `plaintext_body` or `html_body` must be provided. If both are pr
|
|
|
310
311
|
}
|
|
311
312
|
```
|
|
312
313
|
|
|
314
|
+
**Example (delete a draft):**
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"draft_id": "r123456789",
|
|
319
|
+
"delete": true
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
313
323
|
### send_email
|
|
314
324
|
|
|
315
325
|
Send an email directly or from an existing draft.
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { readElicitationConfig } from './config.js';
|
|
3
|
+
/**
|
|
4
|
+
* The set of action values recognized by the elicitation protocol.
|
|
5
|
+
* Includes 'pending' for completeness, though it is filtered before
|
|
6
|
+
* reaching the validation check in pollElicitationStatus.
|
|
7
|
+
*/
|
|
8
|
+
const VALID_ELICITATION_ACTIONS = new Set(['pending', 'accept', 'decline', 'cancel', 'expired']);
|
|
3
9
|
/**
|
|
4
10
|
* Checks whether the connected client supports native form elicitation.
|
|
5
11
|
*/
|
|
@@ -22,6 +28,14 @@ async function nativeElicit(server, message, requestedSchema) {
|
|
|
22
28
|
requestedSchema,
|
|
23
29
|
};
|
|
24
30
|
const result = await server.elicitInput(params);
|
|
31
|
+
// Fail-safe: validate the action even from native elicitation.
|
|
32
|
+
// The TypeScript type says 'accept' | 'decline' | 'cancel', but at runtime
|
|
33
|
+
// the MCP client could return any string over the wire.
|
|
34
|
+
if (!VALID_ELICITATION_ACTIONS.has(result.action)) {
|
|
35
|
+
console.warn(`[elicitation] Unrecognized native elicitation action "${result.action}". ` +
|
|
36
|
+
`Treating as "decline" (fail-safe).`);
|
|
37
|
+
return { action: 'decline' };
|
|
38
|
+
}
|
|
25
39
|
return {
|
|
26
40
|
action: result.action,
|
|
27
41
|
content: result.content ?? undefined,
|
|
@@ -66,6 +80,14 @@ async function pollElicitationStatus(config, requestId, expiresAt) {
|
|
|
66
80
|
}
|
|
67
81
|
const data = (await response.json());
|
|
68
82
|
if (data.action !== 'pending') {
|
|
83
|
+
// Fail-safe: only allow recognized action values through.
|
|
84
|
+
// Unrecognized actions are treated as 'decline' to prevent
|
|
85
|
+
// unintended execution of protected operations.
|
|
86
|
+
if (!VALID_ELICITATION_ACTIONS.has(data.action)) {
|
|
87
|
+
console.warn(`[elicitation] Unrecognized poll action "${data.action}" for request ${requestId}. ` +
|
|
88
|
+
`Treating as "decline" (fail-safe).`);
|
|
89
|
+
return { action: 'decline' };
|
|
90
|
+
}
|
|
69
91
|
return {
|
|
70
92
|
action: data.action,
|
|
71
93
|
content: data.content ?? undefined,
|
package/package.json
CHANGED
|
@@ -3,8 +3,9 @@ import { z } from 'zod';
|
|
|
3
3
|
import type { ClientFactory } from '../server.js';
|
|
4
4
|
export declare const UpsertDraftEmailSchema: z.ZodEffects<z.ZodObject<{
|
|
5
5
|
draft_id: z.ZodOptional<z.ZodString>;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
delete: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
to: z.ZodOptional<z.ZodString>;
|
|
8
|
+
subject: z.ZodOptional<z.ZodString>;
|
|
8
9
|
plaintext_body: z.ZodOptional<z.ZodString>;
|
|
9
10
|
html_body: z.ZodOptional<z.ZodString>;
|
|
10
11
|
cc: z.ZodOptional<z.ZodString>;
|
|
@@ -12,9 +13,10 @@ export declare const UpsertDraftEmailSchema: z.ZodEffects<z.ZodObject<{
|
|
|
12
13
|
thread_id: z.ZodOptional<z.ZodString>;
|
|
13
14
|
reply_to_email_id: z.ZodOptional<z.ZodString>;
|
|
14
15
|
}, "strip", z.ZodTypeAny, {
|
|
15
|
-
to: string;
|
|
16
|
-
subject: string;
|
|
17
16
|
draft_id?: string | undefined;
|
|
17
|
+
delete?: boolean | undefined;
|
|
18
|
+
to?: string | undefined;
|
|
19
|
+
subject?: string | undefined;
|
|
18
20
|
plaintext_body?: string | undefined;
|
|
19
21
|
html_body?: string | undefined;
|
|
20
22
|
cc?: string | undefined;
|
|
@@ -22,9 +24,10 @@ export declare const UpsertDraftEmailSchema: z.ZodEffects<z.ZodObject<{
|
|
|
22
24
|
thread_id?: string | undefined;
|
|
23
25
|
reply_to_email_id?: string | undefined;
|
|
24
26
|
}, {
|
|
25
|
-
to: string;
|
|
26
|
-
subject: string;
|
|
27
27
|
draft_id?: string | undefined;
|
|
28
|
+
delete?: boolean | undefined;
|
|
29
|
+
to?: string | undefined;
|
|
30
|
+
subject?: string | undefined;
|
|
28
31
|
plaintext_body?: string | undefined;
|
|
29
32
|
html_body?: string | undefined;
|
|
30
33
|
cc?: string | undefined;
|
|
@@ -32,9 +35,10 @@ export declare const UpsertDraftEmailSchema: z.ZodEffects<z.ZodObject<{
|
|
|
32
35
|
thread_id?: string | undefined;
|
|
33
36
|
reply_to_email_id?: string | undefined;
|
|
34
37
|
}>, {
|
|
35
|
-
to: string;
|
|
36
|
-
subject: string;
|
|
37
38
|
draft_id?: string | undefined;
|
|
39
|
+
delete?: boolean | undefined;
|
|
40
|
+
to?: string | undefined;
|
|
41
|
+
subject?: string | undefined;
|
|
38
42
|
plaintext_body?: string | undefined;
|
|
39
43
|
html_body?: string | undefined;
|
|
40
44
|
cc?: string | undefined;
|
|
@@ -42,9 +46,10 @@ export declare const UpsertDraftEmailSchema: z.ZodEffects<z.ZodObject<{
|
|
|
42
46
|
thread_id?: string | undefined;
|
|
43
47
|
reply_to_email_id?: string | undefined;
|
|
44
48
|
}, {
|
|
45
|
-
to: string;
|
|
46
|
-
subject: string;
|
|
47
49
|
draft_id?: string | undefined;
|
|
50
|
+
delete?: boolean | undefined;
|
|
51
|
+
to?: string | undefined;
|
|
52
|
+
subject?: string | undefined;
|
|
48
53
|
plaintext_body?: string | undefined;
|
|
49
54
|
html_body?: string | undefined;
|
|
50
55
|
cc?: string | undefined;
|
|
@@ -62,6 +67,10 @@ export declare function upsertDraftEmailTool(server: Server, clientFactory: Clie
|
|
|
62
67
|
type: string;
|
|
63
68
|
description: string;
|
|
64
69
|
};
|
|
70
|
+
delete: {
|
|
71
|
+
type: string;
|
|
72
|
+
description: string;
|
|
73
|
+
};
|
|
65
74
|
to: {
|
|
66
75
|
type: string;
|
|
67
76
|
description: "Recipient email address(es). For multiple recipients, separate with commas.";
|
|
@@ -95,7 +104,7 @@ export declare function upsertDraftEmailTool(server: Server, clientFactory: Clie
|
|
|
95
104
|
description: string;
|
|
96
105
|
};
|
|
97
106
|
};
|
|
98
|
-
required:
|
|
107
|
+
required: never[];
|
|
99
108
|
};
|
|
100
109
|
handler: (args: unknown) => Promise<{
|
|
101
110
|
content: {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { getHeader } from '../utils/email-helpers.js';
|
|
3
3
|
const PARAM_DESCRIPTIONS = {
|
|
4
|
-
draft_id: 'ID of an existing draft to update. If provided, the draft is replaced in-place ' +
|
|
5
|
-
'with the new content. If omitted, a new draft is created. ' +
|
|
4
|
+
draft_id: 'ID of an existing draft to update or delete. If provided, the draft is replaced in-place ' +
|
|
5
|
+
'with the new content (or deleted if delete is true). If omitted, a new draft is created. ' +
|
|
6
6
|
'Get draft IDs from list_draft_emails or from a previous upsert_draft_email response.',
|
|
7
|
+
delete: 'Set to true to delete the draft specified by draft_id. ' +
|
|
8
|
+
'Requires draft_id. All other parameters are ignored when deleting.',
|
|
7
9
|
to: 'Recipient email address(es). For multiple recipients, separate with commas.',
|
|
8
10
|
subject: 'Subject line of the email.',
|
|
9
11
|
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.',
|
|
@@ -18,8 +20,9 @@ const PARAM_DESCRIPTIONS = {
|
|
|
18
20
|
export const UpsertDraftEmailSchema = z
|
|
19
21
|
.object({
|
|
20
22
|
draft_id: z.string().optional().describe(PARAM_DESCRIPTIONS.draft_id),
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
delete: z.boolean().optional().describe(PARAM_DESCRIPTIONS.delete),
|
|
24
|
+
to: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.to),
|
|
25
|
+
subject: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.subject),
|
|
23
26
|
plaintext_body: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.plaintext_body),
|
|
24
27
|
html_body: z.string().min(1).optional().describe(PARAM_DESCRIPTIONS.html_body),
|
|
25
28
|
cc: z.string().optional().describe(PARAM_DESCRIPTIONS.cc),
|
|
@@ -27,26 +30,39 @@ export const UpsertDraftEmailSchema = z
|
|
|
27
30
|
thread_id: z.string().optional().describe(PARAM_DESCRIPTIONS.thread_id),
|
|
28
31
|
reply_to_email_id: z.string().optional().describe(PARAM_DESCRIPTIONS.reply_to_email_id),
|
|
29
32
|
})
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
.superRefine((data, ctx) => {
|
|
34
|
+
if (data.delete) {
|
|
35
|
+
if (!data.draft_id) {
|
|
36
|
+
ctx.addIssue({
|
|
37
|
+
code: z.ZodIssueCode.custom,
|
|
38
|
+
message: 'draft_id is required when delete is true.',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!data.to || !data.subject || (!data.plaintext_body && !data.html_body)) {
|
|
44
|
+
ctx.addIssue({
|
|
45
|
+
code: z.ZodIssueCode.custom,
|
|
46
|
+
message: 'to, subject, and at least one of plaintext_body or html_body must be provided.',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
34
49
|
});
|
|
35
|
-
const TOOL_DESCRIPTION = `Create
|
|
50
|
+
const TOOL_DESCRIPTION = `Create, update, or delete a draft email.
|
|
36
51
|
|
|
37
52
|
**Parameters:**
|
|
38
|
-
- draft_id: ID of an existing draft to update (optional — omit to create a new draft)
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
53
|
+
- draft_id: ID of an existing draft to update or delete (optional — omit to create a new draft)
|
|
54
|
+
- delete: Set to true to delete the draft specified by draft_id (optional)
|
|
55
|
+
- to: Recipient email address(es) (required for create/update)
|
|
56
|
+
- subject: Email subject line (required for create/update)
|
|
57
|
+
- plaintext_body: Plain text body content (at least one of plaintext_body or html_body required for create/update)
|
|
58
|
+
- html_body: HTML body content for rich text formatting (at least one of plaintext_body or html_body required for create/update)
|
|
43
59
|
- cc: CC recipients (optional)
|
|
44
60
|
- bcc: BCC recipients (optional)
|
|
45
61
|
- thread_id: Thread ID to reply to an existing conversation (optional)
|
|
46
62
|
- reply_to_email_id: Email ID to reply to, sets proper reply headers (optional)
|
|
47
63
|
|
|
48
64
|
**Body content:**
|
|
49
|
-
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.
|
|
65
|
+
At least one of plaintext_body or html_body must be provided for create/update. 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.
|
|
50
66
|
|
|
51
67
|
**Creating a reply:**
|
|
52
68
|
To create a draft reply to an existing email:
|
|
@@ -56,11 +72,15 @@ To create a draft reply to an existing email:
|
|
|
56
72
|
**Updating a draft:**
|
|
57
73
|
To update an existing draft, provide the draft_id from a previous upsert_draft_email response or from list_draft_emails. The draft is replaced in-place — all fields must be provided (not just the ones you want to change).
|
|
58
74
|
|
|
75
|
+
**Deleting a draft:**
|
|
76
|
+
To delete a draft, provide the draft_id and set delete to true. All other parameters are ignored when deleting.
|
|
77
|
+
|
|
59
78
|
**Use cases:**
|
|
60
79
|
- Draft a new email for later review
|
|
61
80
|
- Prepare a reply to an email conversation
|
|
62
81
|
- Revise a draft after user feedback (without creating duplicates)
|
|
63
82
|
- Save an email without sending it immediately
|
|
83
|
+
- Delete a corrupted or unwanted draft
|
|
64
84
|
|
|
65
85
|
**Note:** The draft will be saved in Gmail's Drafts folder. Use send_email with from_draft_id to send it.`;
|
|
66
86
|
export function upsertDraftEmailTool(server, clientFactory) {
|
|
@@ -74,6 +94,10 @@ export function upsertDraftEmailTool(server, clientFactory) {
|
|
|
74
94
|
type: 'string',
|
|
75
95
|
description: PARAM_DESCRIPTIONS.draft_id,
|
|
76
96
|
},
|
|
97
|
+
delete: {
|
|
98
|
+
type: 'boolean',
|
|
99
|
+
description: PARAM_DESCRIPTIONS.delete,
|
|
100
|
+
},
|
|
77
101
|
to: {
|
|
78
102
|
type: 'string',
|
|
79
103
|
description: PARAM_DESCRIPTIONS.to,
|
|
@@ -107,12 +131,24 @@ export function upsertDraftEmailTool(server, clientFactory) {
|
|
|
107
131
|
description: PARAM_DESCRIPTIONS.reply_to_email_id,
|
|
108
132
|
},
|
|
109
133
|
},
|
|
110
|
-
required: [
|
|
134
|
+
required: [],
|
|
111
135
|
},
|
|
112
136
|
handler: async (args) => {
|
|
113
137
|
try {
|
|
114
138
|
const parsed = UpsertDraftEmailSchema.parse(args ?? {});
|
|
115
139
|
const client = clientFactory();
|
|
140
|
+
// Delete mode
|
|
141
|
+
if (parsed.delete) {
|
|
142
|
+
await client.deleteDraft(parsed.draft_id);
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: 'text',
|
|
147
|
+
text: `Draft deleted successfully!\n\n**Draft ID:** ${parsed.draft_id}`,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
116
152
|
let inReplyTo;
|
|
117
153
|
let references;
|
|
118
154
|
// If replying to an email, get the Message-ID for proper threading
|
|
@@ -176,11 +212,17 @@ export function upsertDraftEmailTool(server, clientFactory) {
|
|
|
176
212
|
};
|
|
177
213
|
}
|
|
178
214
|
catch (error) {
|
|
215
|
+
const typedArgs = args;
|
|
216
|
+
const errorAction = typedArgs?.delete
|
|
217
|
+
? 'deleting'
|
|
218
|
+
: typedArgs?.draft_id
|
|
219
|
+
? 'updating'
|
|
220
|
+
: 'creating';
|
|
179
221
|
return {
|
|
180
222
|
content: [
|
|
181
223
|
{
|
|
182
224
|
type: 'text',
|
|
183
|
-
text: `Error ${
|
|
225
|
+
text: `Error ${errorAction} draft: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
184
226
|
},
|
|
185
227
|
],
|
|
186
228
|
isError: true,
|
|
@@ -102,12 +102,12 @@ export declare function sendEmailTool(server: Server, clientFactory: ClientFacto
|
|
|
102
102
|
type: string;
|
|
103
103
|
text: string;
|
|
104
104
|
}[];
|
|
105
|
-
isError
|
|
105
|
+
isError: boolean;
|
|
106
106
|
} | {
|
|
107
107
|
content: {
|
|
108
108
|
type: string;
|
|
109
109
|
text: string;
|
|
110
110
|
}[];
|
|
111
|
-
isError
|
|
111
|
+
isError?: undefined;
|
|
112
112
|
}>;
|
|
113
113
|
};
|
|
@@ -139,7 +139,20 @@ export function sendEmailTool(server, clientFactory) {
|
|
|
139
139
|
'com.pulsemcp/tool-name': 'send_email',
|
|
140
140
|
},
|
|
141
141
|
}, elicitationConfig);
|
|
142
|
-
|
|
142
|
+
// Fail-safe: only proceed on explicit 'accept'.
|
|
143
|
+
// Any other action (decline, cancel, expired, or unrecognized) blocks the send.
|
|
144
|
+
if (confirmation.action !== 'accept') {
|
|
145
|
+
if (confirmation.action === 'expired') {
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: 'text',
|
|
150
|
+
text: 'Email sending confirmation expired. Please try again.',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
isError: true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
143
156
|
return {
|
|
144
157
|
content: [
|
|
145
158
|
{
|
|
@@ -149,19 +162,8 @@ export function sendEmailTool(server, clientFactory) {
|
|
|
149
162
|
],
|
|
150
163
|
};
|
|
151
164
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
content: [
|
|
155
|
-
{
|
|
156
|
-
type: 'text',
|
|
157
|
-
text: 'Email sending confirmation expired. Please try again.',
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
isError: true,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
// action === 'accept' with content.confirm === true, or
|
|
164
|
-
// action === 'accept' from disabled mode (no content)
|
|
165
|
+
// We reach here only when action === 'accept'.
|
|
166
|
+
// Still check if the user explicitly unchecked the confirm checkbox.
|
|
165
167
|
if (confirmation.content &&
|
|
166
168
|
'confirm' in confirmation.content &&
|
|
167
169
|
confirmation.content.confirm === false) {
|