gmail-workspace-mcp-server 0.0.3 → 0.0.4
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 +145 -15
- package/build/index.integration-with-mock.js +140 -4
- package/build/index.js +23 -6
- package/package.json +1 -1
- package/shared/gmail-client/lib/drafts.d.ts +42 -0
- package/shared/gmail-client/lib/drafts.js +81 -0
- package/shared/gmail-client/lib/mime-utils.d.ts +20 -0
- package/shared/gmail-client/lib/mime-utils.js +38 -0
- package/shared/gmail-client/lib/modify-message.d.ts +9 -0
- package/shared/gmail-client/lib/modify-message.js +20 -0
- package/shared/gmail-client/lib/send-message.d.ts +18 -0
- package/shared/gmail-client/lib/send-message.js +40 -0
- package/shared/index.d.ts +2 -2
- package/shared/index.js +2 -2
- package/shared/server.d.ts +108 -1
- package/shared/server.js +43 -3
- package/shared/tools/change-email-conversation.d.ts +66 -0
- package/shared/tools/change-email-conversation.js +148 -0
- package/shared/tools/draft-email.d.ts +79 -0
- package/shared/tools/draft-email.js +150 -0
- package/shared/tools/{get-email.d.ts → get-email-conversation.d.ts} +2 -2
- package/shared/tools/{get-email.js → get-email-conversation.js} +8 -8
- package/shared/tools/{list-recent-emails.d.ts → list-email-conversations.d.ts} +28 -13
- package/shared/tools/list-email-conversations.js +150 -0
- package/shared/tools/search-email-conversations.d.ts +45 -0
- package/shared/tools/search-email-conversations.js +110 -0
- package/shared/tools/send-email.d.ts +104 -0
- package/shared/tools/send-email.js +181 -0
- package/shared/tools.d.ts +19 -2
- package/shared/tools.js +56 -8
- package/shared/utils/email-helpers.d.ts +4 -0
- package/shared/utils/email-helpers.js +15 -0
- package/shared/tools/list-recent-emails.js +0 -133
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getHeader } from '../utils/email-helpers.js';
|
|
3
|
+
const PARAM_DESCRIPTIONS = {
|
|
4
|
+
to: 'Recipient email address(es). For multiple recipients, separate with commas.',
|
|
5
|
+
subject: 'Subject line of the email.',
|
|
6
|
+
body: 'Plain text body content of the email.',
|
|
7
|
+
cc: 'CC recipient email address(es). For multiple, separate with commas.',
|
|
8
|
+
bcc: 'BCC recipient email address(es). For multiple, separate with commas.',
|
|
9
|
+
thread_id: 'Thread ID to add this draft to an existing conversation. ' +
|
|
10
|
+
'Get this from get_email_conversation. If provided, the draft will be a reply in that thread.',
|
|
11
|
+
reply_to_email_id: 'Email ID to reply to. If provided, the draft will be formatted as a reply ' +
|
|
12
|
+
'with proper In-Reply-To and References headers. Also requires thread_id.',
|
|
13
|
+
};
|
|
14
|
+
export const DraftEmailSchema = z.object({
|
|
15
|
+
to: z.string().min(1).describe(PARAM_DESCRIPTIONS.to),
|
|
16
|
+
subject: z.string().min(1).describe(PARAM_DESCRIPTIONS.subject),
|
|
17
|
+
body: z.string().min(1).describe(PARAM_DESCRIPTIONS.body),
|
|
18
|
+
cc: z.string().optional().describe(PARAM_DESCRIPTIONS.cc),
|
|
19
|
+
bcc: z.string().optional().describe(PARAM_DESCRIPTIONS.bcc),
|
|
20
|
+
thread_id: z.string().optional().describe(PARAM_DESCRIPTIONS.thread_id),
|
|
21
|
+
reply_to_email_id: z.string().optional().describe(PARAM_DESCRIPTIONS.reply_to_email_id),
|
|
22
|
+
});
|
|
23
|
+
const TOOL_DESCRIPTION = `Create a draft email that can be reviewed and sent later.
|
|
24
|
+
|
|
25
|
+
**Parameters:**
|
|
26
|
+
- to: Recipient email address(es) (required)
|
|
27
|
+
- subject: Email subject line (required)
|
|
28
|
+
- body: Plain text body content (required)
|
|
29
|
+
- cc: CC recipients (optional)
|
|
30
|
+
- bcc: BCC recipients (optional)
|
|
31
|
+
- thread_id: Thread ID to reply to an existing conversation (optional)
|
|
32
|
+
- reply_to_email_id: Email ID to reply to, sets proper reply headers (optional)
|
|
33
|
+
|
|
34
|
+
**Creating a reply:**
|
|
35
|
+
To create a draft reply to an existing email:
|
|
36
|
+
1. Get the thread_id and email_id from get_email_conversation
|
|
37
|
+
2. Provide both thread_id and reply_to_email_id parameters
|
|
38
|
+
|
|
39
|
+
**Use cases:**
|
|
40
|
+
- Draft a new email for later review
|
|
41
|
+
- Prepare a reply to an email conversation
|
|
42
|
+
- Save an email without sending it immediately
|
|
43
|
+
|
|
44
|
+
**Note:** The draft will be saved in Gmail's Drafts folder. Use send_email with from_draft_id to send it.`;
|
|
45
|
+
export function draftEmailTool(server, clientFactory) {
|
|
46
|
+
return {
|
|
47
|
+
name: 'draft_email',
|
|
48
|
+
description: TOOL_DESCRIPTION,
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
to: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: PARAM_DESCRIPTIONS.to,
|
|
55
|
+
},
|
|
56
|
+
subject: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: PARAM_DESCRIPTIONS.subject,
|
|
59
|
+
},
|
|
60
|
+
body: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
description: PARAM_DESCRIPTIONS.body,
|
|
63
|
+
},
|
|
64
|
+
cc: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
description: PARAM_DESCRIPTIONS.cc,
|
|
67
|
+
},
|
|
68
|
+
bcc: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: PARAM_DESCRIPTIONS.bcc,
|
|
71
|
+
},
|
|
72
|
+
thread_id: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: PARAM_DESCRIPTIONS.thread_id,
|
|
75
|
+
},
|
|
76
|
+
reply_to_email_id: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: PARAM_DESCRIPTIONS.reply_to_email_id,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
required: ['to', 'subject', 'body'],
|
|
82
|
+
},
|
|
83
|
+
handler: async (args) => {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = DraftEmailSchema.parse(args ?? {});
|
|
86
|
+
const client = clientFactory();
|
|
87
|
+
let inReplyTo;
|
|
88
|
+
let references;
|
|
89
|
+
// If replying to an email, get the Message-ID for proper threading
|
|
90
|
+
if (parsed.reply_to_email_id && parsed.thread_id) {
|
|
91
|
+
const originalEmail = await client.getMessage(parsed.reply_to_email_id, {
|
|
92
|
+
format: 'metadata',
|
|
93
|
+
metadataHeaders: ['Message-ID', 'References'],
|
|
94
|
+
});
|
|
95
|
+
const messageId = getHeader(originalEmail, 'Message-ID');
|
|
96
|
+
const originalReferences = getHeader(originalEmail, 'References');
|
|
97
|
+
if (messageId) {
|
|
98
|
+
inReplyTo = messageId;
|
|
99
|
+
// Build references chain
|
|
100
|
+
references = originalReferences ? `${originalReferences} ${messageId}` : messageId;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const draft = await client.createDraft({
|
|
104
|
+
to: parsed.to,
|
|
105
|
+
subject: parsed.subject,
|
|
106
|
+
body: parsed.body,
|
|
107
|
+
cc: parsed.cc,
|
|
108
|
+
bcc: parsed.bcc,
|
|
109
|
+
threadId: parsed.thread_id,
|
|
110
|
+
inReplyTo,
|
|
111
|
+
references,
|
|
112
|
+
});
|
|
113
|
+
let responseText = `Draft created successfully!\n\n**Draft ID:** ${draft.id}`;
|
|
114
|
+
if (parsed.thread_id) {
|
|
115
|
+
responseText += `\n**Thread ID:** ${parsed.thread_id}`;
|
|
116
|
+
responseText += '\n\nThis draft is a reply in an existing conversation.';
|
|
117
|
+
}
|
|
118
|
+
responseText += `\n\n**To:** ${parsed.to}`;
|
|
119
|
+
responseText += `\n**Subject:** ${parsed.subject}`;
|
|
120
|
+
if (parsed.cc) {
|
|
121
|
+
responseText += `\n**CC:** ${parsed.cc}`;
|
|
122
|
+
}
|
|
123
|
+
if (parsed.bcc) {
|
|
124
|
+
responseText += `\n**BCC:** ${parsed.bcc}`;
|
|
125
|
+
}
|
|
126
|
+
responseText +=
|
|
127
|
+
"\n\nUse send_email with from_draft_id parameter to send this draft, or find it in Gmail's Drafts folder.";
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: responseText,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
return {
|
|
139
|
+
content: [
|
|
140
|
+
{
|
|
141
|
+
type: 'text',
|
|
142
|
+
text: `Error creating draft: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
isError: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
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
|
|
4
|
+
export declare const GetEmailConversationSchema: z.ZodObject<{
|
|
5
5
|
email_id: z.ZodString;
|
|
6
6
|
}, "strip", z.ZodTypeAny, {
|
|
7
7
|
email_id: string;
|
|
8
8
|
}, {
|
|
9
9
|
email_id: string;
|
|
10
10
|
}>;
|
|
11
|
-
export declare function
|
|
11
|
+
export declare function getEmailConversationTool(server: Server, clientFactory: ClientFactory): {
|
|
12
12
|
name: string;
|
|
13
13
|
description: string;
|
|
14
14
|
inputSchema: {
|
|
@@ -2,12 +2,12 @@ import { z } from 'zod';
|
|
|
2
2
|
import { getHeader } from '../utils/email-helpers.js';
|
|
3
3
|
const PARAM_DESCRIPTIONS = {
|
|
4
4
|
email_id: 'The unique identifier of the email to retrieve. ' +
|
|
5
|
-
'Obtain this from
|
|
5
|
+
'Obtain this from list_email_conversations or search_email_conversations.',
|
|
6
6
|
};
|
|
7
|
-
export const
|
|
7
|
+
export const GetEmailConversationSchema = z.object({
|
|
8
8
|
email_id: z.string().min(1).describe(PARAM_DESCRIPTIONS.email_id),
|
|
9
9
|
});
|
|
10
|
-
const TOOL_DESCRIPTION = `Retrieve the full content of a specific email by its ID.
|
|
10
|
+
const TOOL_DESCRIPTION = `Retrieve the full content of a specific email conversation by its ID.
|
|
11
11
|
|
|
12
12
|
Returns the complete email including headers, body content, and attachment information.
|
|
13
13
|
|
|
@@ -22,11 +22,11 @@ Full email details including:
|
|
|
22
22
|
- Labels assigned to the email
|
|
23
23
|
|
|
24
24
|
**Use cases:**
|
|
25
|
-
- Read the full content of an email after listing
|
|
25
|
+
- Read the full content of an email after listing conversations
|
|
26
26
|
- Extract specific information from an email body
|
|
27
27
|
- Check attachment details
|
|
28
28
|
|
|
29
|
-
**Note:** Use
|
|
29
|
+
**Note:** Use list_email_conversations or search_email_conversations first to get email IDs.`;
|
|
30
30
|
/**
|
|
31
31
|
* Decodes base64url encoded content
|
|
32
32
|
*/
|
|
@@ -145,9 +145,9 @@ ${body}`;
|
|
|
145
145
|
}
|
|
146
146
|
return output;
|
|
147
147
|
}
|
|
148
|
-
export function
|
|
148
|
+
export function getEmailConversationTool(server, clientFactory) {
|
|
149
149
|
return {
|
|
150
|
-
name: '
|
|
150
|
+
name: 'get_email_conversation',
|
|
151
151
|
description: TOOL_DESCRIPTION,
|
|
152
152
|
inputSchema: {
|
|
153
153
|
type: 'object',
|
|
@@ -161,7 +161,7 @@ export function getEmailTool(server, clientFactory) {
|
|
|
161
161
|
},
|
|
162
162
|
handler: async (args) => {
|
|
163
163
|
try {
|
|
164
|
-
const parsed =
|
|
164
|
+
const parsed = GetEmailConversationSchema.parse(args ?? {});
|
|
165
165
|
const client = clientFactory();
|
|
166
166
|
const email = await client.getMessage(parsed.email_id, {
|
|
167
167
|
format: 'full',
|
|
@@ -1,39 +1,54 @@
|
|
|
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
|
|
5
|
-
|
|
4
|
+
export declare const ListEmailConversationsSchema: z.ZodObject<{
|
|
5
|
+
count: z.ZodDefault<z.ZodNumber>;
|
|
6
6
|
labels: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
7
|
-
|
|
7
|
+
sort_by: z.ZodDefault<z.ZodEnum<["recent", "oldest"]>>;
|
|
8
|
+
after: z.ZodOptional<z.ZodString>;
|
|
9
|
+
before: z.ZodOptional<z.ZodString>;
|
|
8
10
|
}, "strip", z.ZodTypeAny, {
|
|
9
|
-
|
|
11
|
+
count: number;
|
|
10
12
|
labels: string;
|
|
11
|
-
|
|
13
|
+
sort_by: "recent" | "oldest";
|
|
14
|
+
after?: string | undefined;
|
|
15
|
+
before?: string | undefined;
|
|
12
16
|
}, {
|
|
13
|
-
|
|
17
|
+
count?: number | undefined;
|
|
14
18
|
labels?: string | undefined;
|
|
15
|
-
|
|
19
|
+
sort_by?: "recent" | "oldest" | undefined;
|
|
20
|
+
after?: string | undefined;
|
|
21
|
+
before?: string | undefined;
|
|
16
22
|
}>;
|
|
17
|
-
export declare function
|
|
23
|
+
export declare function listEmailConversationsTool(server: Server, clientFactory: ClientFactory): {
|
|
18
24
|
name: string;
|
|
19
25
|
description: string;
|
|
20
26
|
inputSchema: {
|
|
21
27
|
type: "object";
|
|
22
28
|
properties: {
|
|
23
|
-
|
|
29
|
+
count: {
|
|
24
30
|
type: string;
|
|
25
31
|
default: number;
|
|
26
|
-
description:
|
|
32
|
+
description: "Maximum number of email conversations to return. Default: 10. Max: 100.";
|
|
27
33
|
};
|
|
28
34
|
labels: {
|
|
29
35
|
type: string;
|
|
30
36
|
default: string;
|
|
31
37
|
description: string;
|
|
32
38
|
};
|
|
33
|
-
|
|
39
|
+
sort_by: {
|
|
34
40
|
type: string;
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
enum: string[];
|
|
42
|
+
default: string;
|
|
43
|
+
description: string;
|
|
44
|
+
};
|
|
45
|
+
after: {
|
|
46
|
+
type: string;
|
|
47
|
+
description: string;
|
|
48
|
+
};
|
|
49
|
+
before: {
|
|
50
|
+
type: string;
|
|
51
|
+
description: string;
|
|
37
52
|
};
|
|
38
53
|
};
|
|
39
54
|
required: never[];
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatEmail } from '../utils/email-helpers.js';
|
|
3
|
+
const PARAM_DESCRIPTIONS = {
|
|
4
|
+
count: 'Maximum number of email conversations to return. Default: 10. Max: 100.',
|
|
5
|
+
labels: 'Comma-separated list of label IDs to filter by. Default: INBOX. ' +
|
|
6
|
+
'Common labels: INBOX, SENT, DRAFTS, SPAM, TRASH, STARRED, IMPORTANT, UNREAD.',
|
|
7
|
+
sort_by: 'Sort order for results. Default: recent. ' +
|
|
8
|
+
'Options: recent (newest first), oldest (oldest first).',
|
|
9
|
+
after: 'Only return emails after this datetime (exclusive). ' +
|
|
10
|
+
'ISO 8601 format in UTC (e.g., 2024-01-15T14:30:00Z).',
|
|
11
|
+
before: 'Only return emails before this datetime (exclusive). ' +
|
|
12
|
+
'ISO 8601 format in UTC (e.g., 2024-01-15T14:30:00Z).',
|
|
13
|
+
};
|
|
14
|
+
export const ListEmailConversationsSchema = z.object({
|
|
15
|
+
count: z.number().positive().max(100).default(10).describe(PARAM_DESCRIPTIONS.count),
|
|
16
|
+
labels: z.string().optional().default('INBOX').describe(PARAM_DESCRIPTIONS.labels),
|
|
17
|
+
sort_by: z.enum(['recent', 'oldest']).default('recent').describe(PARAM_DESCRIPTIONS.sort_by),
|
|
18
|
+
after: z.string().optional().describe(PARAM_DESCRIPTIONS.after),
|
|
19
|
+
before: z.string().optional().describe(PARAM_DESCRIPTIONS.before),
|
|
20
|
+
});
|
|
21
|
+
const TOOL_DESCRIPTION = `List email conversations from Gmail.
|
|
22
|
+
|
|
23
|
+
Returns a list of email conversations with their subject, sender, date, and a snippet preview. Use get_email_conversation to retrieve the full content of a specific conversation.
|
|
24
|
+
|
|
25
|
+
**Parameters:**
|
|
26
|
+
- count: Maximum conversations to return (default: 10, max: 100)
|
|
27
|
+
- labels: Which labels/folders to search (default: INBOX)
|
|
28
|
+
- sort_by: Sort order - "recent" (newest first) or "oldest" (default: recent)
|
|
29
|
+
- after: Only return emails after this datetime, exclusive (ISO 8601 UTC, e.g., 2024-01-15T14:30:00Z)
|
|
30
|
+
- before: Only return emails before this datetime, exclusive (ISO 8601 UTC, e.g., 2024-01-15T14:30:00Z)
|
|
31
|
+
|
|
32
|
+
**Returns:**
|
|
33
|
+
A formatted list of email conversations with:
|
|
34
|
+
- Email ID (needed for get_email_conversation)
|
|
35
|
+
- Thread ID
|
|
36
|
+
- Subject line
|
|
37
|
+
- Sender (From)
|
|
38
|
+
- Date received
|
|
39
|
+
- Snippet preview
|
|
40
|
+
|
|
41
|
+
**Use cases:**
|
|
42
|
+
- Check recent inbox activity
|
|
43
|
+
- List emails from specific labels like SENT or STARRED
|
|
44
|
+
- Get oldest emails first for processing backlogs
|
|
45
|
+
- Filter emails by date/time range using after and/or before
|
|
46
|
+
|
|
47
|
+
**Note:** This tool only returns email metadata and snippets. Use get_email_conversation with an email ID to retrieve the full message content.`;
|
|
48
|
+
export function listEmailConversationsTool(server, clientFactory) {
|
|
49
|
+
return {
|
|
50
|
+
name: 'list_email_conversations',
|
|
51
|
+
description: TOOL_DESCRIPTION,
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
count: {
|
|
56
|
+
type: 'number',
|
|
57
|
+
default: 10,
|
|
58
|
+
description: PARAM_DESCRIPTIONS.count,
|
|
59
|
+
},
|
|
60
|
+
labels: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
default: 'INBOX',
|
|
63
|
+
description: PARAM_DESCRIPTIONS.labels,
|
|
64
|
+
},
|
|
65
|
+
sort_by: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
enum: ['recent', 'oldest'],
|
|
68
|
+
default: 'recent',
|
|
69
|
+
description: PARAM_DESCRIPTIONS.sort_by,
|
|
70
|
+
},
|
|
71
|
+
after: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: PARAM_DESCRIPTIONS.after,
|
|
74
|
+
},
|
|
75
|
+
before: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
description: PARAM_DESCRIPTIONS.before,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
required: [],
|
|
81
|
+
},
|
|
82
|
+
handler: async (args) => {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = ListEmailConversationsSchema.parse(args ?? {});
|
|
85
|
+
const client = clientFactory();
|
|
86
|
+
// Parse labels
|
|
87
|
+
const labelIds = parsed.labels.split(',').map((l) => l.trim().toUpperCase());
|
|
88
|
+
// Build query string for datetime filtering
|
|
89
|
+
// Gmail uses Unix timestamps (seconds since epoch) for after: and before: operators
|
|
90
|
+
const queryParts = [];
|
|
91
|
+
if (parsed.after) {
|
|
92
|
+
const timestamp = Math.floor(new Date(parsed.after).getTime() / 1000);
|
|
93
|
+
queryParts.push(`after:${timestamp}`);
|
|
94
|
+
}
|
|
95
|
+
if (parsed.before) {
|
|
96
|
+
const timestamp = Math.floor(new Date(parsed.before).getTime() / 1000);
|
|
97
|
+
queryParts.push(`before:${timestamp}`);
|
|
98
|
+
}
|
|
99
|
+
const query = queryParts.length > 0 ? queryParts.join(' ') : undefined;
|
|
100
|
+
// List messages
|
|
101
|
+
const { messages } = await client.listMessages({
|
|
102
|
+
maxResults: parsed.count,
|
|
103
|
+
labelIds,
|
|
104
|
+
q: query,
|
|
105
|
+
});
|
|
106
|
+
if (messages.length === 0) {
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: `No email conversations found with labels: ${labelIds.join(', ')}`,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Fetch full details for each message
|
|
117
|
+
const emailDetails = await Promise.all(messages.map((msg) => client.getMessage(msg.id, {
|
|
118
|
+
format: 'metadata',
|
|
119
|
+
metadataHeaders: ['Subject', 'From', 'Date'],
|
|
120
|
+
})));
|
|
121
|
+
// Sort based on sort_by parameter
|
|
122
|
+
const sortedEmails = [...emailDetails].sort((a, b) => {
|
|
123
|
+
const dateA = parseInt(a.internalDate, 10);
|
|
124
|
+
const dateB = parseInt(b.internalDate, 10);
|
|
125
|
+
return parsed.sort_by === 'recent' ? dateB - dateA : dateA - dateB;
|
|
126
|
+
});
|
|
127
|
+
const formattedEmails = sortedEmails.map(formatEmail).join('\n\n---\n\n');
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: `Found ${messages.length} email conversation(s):\n\n${formattedEmails}`,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
return {
|
|
139
|
+
content: [
|
|
140
|
+
{
|
|
141
|
+
type: 'text',
|
|
142
|
+
text: `Error listing email conversations: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
isError: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { ClientFactory } from '../server.js';
|
|
4
|
+
export declare const SearchEmailConversationsSchema: z.ZodObject<{
|
|
5
|
+
query: z.ZodString;
|
|
6
|
+
count: z.ZodDefault<z.ZodNumber>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
count: number;
|
|
9
|
+
query: string;
|
|
10
|
+
}, {
|
|
11
|
+
query: string;
|
|
12
|
+
count?: number | undefined;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function searchEmailConversationsTool(server: Server, clientFactory: ClientFactory): {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object";
|
|
19
|
+
properties: {
|
|
20
|
+
query: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
count: {
|
|
25
|
+
type: string;
|
|
26
|
+
default: number;
|
|
27
|
+
description: "Maximum number of results to return. Default: 10. Max: 100.";
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
required: string[];
|
|
31
|
+
};
|
|
32
|
+
handler: (args: unknown) => Promise<{
|
|
33
|
+
content: {
|
|
34
|
+
type: string;
|
|
35
|
+
text: string;
|
|
36
|
+
}[];
|
|
37
|
+
isError?: undefined;
|
|
38
|
+
} | {
|
|
39
|
+
content: {
|
|
40
|
+
type: string;
|
|
41
|
+
text: string;
|
|
42
|
+
}[];
|
|
43
|
+
isError: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatEmail } from '../utils/email-helpers.js';
|
|
3
|
+
const PARAM_DESCRIPTIONS = {
|
|
4
|
+
query: 'Gmail search query. Supports all Gmail search operators. ' +
|
|
5
|
+
'Examples: "from:user@example.com", "subject:meeting", "is:unread", "has:attachment", ' +
|
|
6
|
+
'"after:2024/01/01", "before:2024/12/31", "in:inbox", "newer_than:7d".',
|
|
7
|
+
count: 'Maximum number of results to return. Default: 10. Max: 100.',
|
|
8
|
+
};
|
|
9
|
+
export const SearchEmailConversationsSchema = z.object({
|
|
10
|
+
query: z.string().min(1).describe(PARAM_DESCRIPTIONS.query),
|
|
11
|
+
count: z.number().positive().max(100).default(10).describe(PARAM_DESCRIPTIONS.count),
|
|
12
|
+
});
|
|
13
|
+
const TOOL_DESCRIPTION = `Search email conversations using Gmail's powerful search syntax.
|
|
14
|
+
|
|
15
|
+
**Parameters:**
|
|
16
|
+
- query: Gmail search query (required)
|
|
17
|
+
- count: Maximum results to return (default: 10, max: 100)
|
|
18
|
+
|
|
19
|
+
**Search operators:**
|
|
20
|
+
- from:user@example.com - Emails from specific sender
|
|
21
|
+
- to:user@example.com - Emails sent to specific recipient
|
|
22
|
+
- subject:keyword - Emails with keyword in subject
|
|
23
|
+
- is:unread / is:read - Unread or read emails
|
|
24
|
+
- is:starred - Starred emails
|
|
25
|
+
- has:attachment - Emails with attachments
|
|
26
|
+
- filename:pdf - Emails with specific attachment type
|
|
27
|
+
- after:2024/01/01 - Emails after a date
|
|
28
|
+
- before:2024/12/31 - Emails before a date
|
|
29
|
+
- newer_than:7d - Emails from the last 7 days
|
|
30
|
+
- older_than:1m - Emails older than 1 month
|
|
31
|
+
- in:inbox / in:sent / in:drafts - Emails in specific folder
|
|
32
|
+
- label:work - Emails with specific label
|
|
33
|
+
- "exact phrase" - Search for exact phrase
|
|
34
|
+
|
|
35
|
+
**Combining operators:**
|
|
36
|
+
- Use spaces to AND operators: "from:alice is:unread"
|
|
37
|
+
- Use OR for alternatives: "from:alice OR from:bob"
|
|
38
|
+
- Use - to exclude: "subject:meeting -subject:canceled"
|
|
39
|
+
|
|
40
|
+
**Returns:**
|
|
41
|
+
A formatted list of matching emails with ID, Thread ID, Subject, From, Date, and snippet.
|
|
42
|
+
|
|
43
|
+
**Note:** Use get_email_conversation with an email ID to retrieve full message content.`;
|
|
44
|
+
export function searchEmailConversationsTool(server, clientFactory) {
|
|
45
|
+
return {
|
|
46
|
+
name: 'search_email_conversations',
|
|
47
|
+
description: TOOL_DESCRIPTION,
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
query: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: PARAM_DESCRIPTIONS.query,
|
|
54
|
+
},
|
|
55
|
+
count: {
|
|
56
|
+
type: 'number',
|
|
57
|
+
default: 10,
|
|
58
|
+
description: PARAM_DESCRIPTIONS.count,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
required: ['query'],
|
|
62
|
+
},
|
|
63
|
+
handler: async (args) => {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = SearchEmailConversationsSchema.parse(args ?? {});
|
|
66
|
+
const client = clientFactory();
|
|
67
|
+
// Search messages using the query
|
|
68
|
+
const { messages } = await client.listMessages({
|
|
69
|
+
q: parsed.query,
|
|
70
|
+
maxResults: parsed.count,
|
|
71
|
+
});
|
|
72
|
+
if (messages.length === 0) {
|
|
73
|
+
return {
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: 'text',
|
|
77
|
+
text: `No emails found matching query: "${parsed.query}"`,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Fetch full details for each message
|
|
83
|
+
const emailDetails = await Promise.all(messages.map((msg) => client.getMessage(msg.id, {
|
|
84
|
+
format: 'metadata',
|
|
85
|
+
metadataHeaders: ['Subject', 'From', 'Date'],
|
|
86
|
+
})));
|
|
87
|
+
const formattedEmails = emailDetails.map(formatEmail).join('\n\n---\n\n');
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: 'text',
|
|
92
|
+
text: `Found ${messages.length} email(s) matching "${parsed.query}":\n\n${formattedEmails}`,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return {
|
|
99
|
+
content: [
|
|
100
|
+
{
|
|
101
|
+
type: 'text',
|
|
102
|
+
text: `Error searching emails: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
isError: true,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|