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.
Files changed (33) hide show
  1. package/README.md +145 -15
  2. package/build/index.integration-with-mock.js +140 -4
  3. package/build/index.js +23 -6
  4. package/package.json +1 -1
  5. package/shared/gmail-client/lib/drafts.d.ts +42 -0
  6. package/shared/gmail-client/lib/drafts.js +81 -0
  7. package/shared/gmail-client/lib/mime-utils.d.ts +20 -0
  8. package/shared/gmail-client/lib/mime-utils.js +38 -0
  9. package/shared/gmail-client/lib/modify-message.d.ts +9 -0
  10. package/shared/gmail-client/lib/modify-message.js +20 -0
  11. package/shared/gmail-client/lib/send-message.d.ts +18 -0
  12. package/shared/gmail-client/lib/send-message.js +40 -0
  13. package/shared/index.d.ts +2 -2
  14. package/shared/index.js +2 -2
  15. package/shared/server.d.ts +108 -1
  16. package/shared/server.js +43 -3
  17. package/shared/tools/change-email-conversation.d.ts +66 -0
  18. package/shared/tools/change-email-conversation.js +148 -0
  19. package/shared/tools/draft-email.d.ts +79 -0
  20. package/shared/tools/draft-email.js +150 -0
  21. package/shared/tools/{get-email.d.ts → get-email-conversation.d.ts} +2 -2
  22. package/shared/tools/{get-email.js → get-email-conversation.js} +8 -8
  23. package/shared/tools/{list-recent-emails.d.ts → list-email-conversations.d.ts} +28 -13
  24. package/shared/tools/list-email-conversations.js +150 -0
  25. package/shared/tools/search-email-conversations.d.ts +45 -0
  26. package/shared/tools/search-email-conversations.js +110 -0
  27. package/shared/tools/send-email.d.ts +104 -0
  28. package/shared/tools/send-email.js +181 -0
  29. package/shared/tools.d.ts +19 -2
  30. package/shared/tools.js +56 -8
  31. package/shared/utils/email-helpers.d.ts +4 -0
  32. package/shared/utils/email-helpers.js +15 -0
  33. package/shared/tools/list-recent-emails.js +0 -133
@@ -0,0 +1,104 @@
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 SendEmailSchema: z.ZodEffects<z.ZodObject<{
5
+ to: z.ZodOptional<z.ZodString>;
6
+ subject: z.ZodOptional<z.ZodString>;
7
+ body: z.ZodOptional<z.ZodString>;
8
+ cc: z.ZodOptional<z.ZodString>;
9
+ bcc: z.ZodOptional<z.ZodString>;
10
+ thread_id: z.ZodOptional<z.ZodString>;
11
+ reply_to_email_id: z.ZodOptional<z.ZodString>;
12
+ from_draft_id: z.ZodOptional<z.ZodString>;
13
+ }, "strip", z.ZodTypeAny, {
14
+ body?: string | undefined;
15
+ to?: string | undefined;
16
+ subject?: string | undefined;
17
+ cc?: string | undefined;
18
+ bcc?: string | undefined;
19
+ thread_id?: string | undefined;
20
+ reply_to_email_id?: string | undefined;
21
+ from_draft_id?: string | undefined;
22
+ }, {
23
+ body?: string | undefined;
24
+ to?: string | undefined;
25
+ subject?: string | undefined;
26
+ cc?: string | undefined;
27
+ bcc?: string | undefined;
28
+ thread_id?: string | undefined;
29
+ reply_to_email_id?: string | undefined;
30
+ from_draft_id?: string | undefined;
31
+ }>, {
32
+ body?: string | undefined;
33
+ to?: string | undefined;
34
+ subject?: string | undefined;
35
+ cc?: string | undefined;
36
+ bcc?: string | undefined;
37
+ thread_id?: string | undefined;
38
+ reply_to_email_id?: string | undefined;
39
+ from_draft_id?: string | undefined;
40
+ }, {
41
+ body?: string | undefined;
42
+ to?: string | undefined;
43
+ subject?: string | undefined;
44
+ cc?: string | undefined;
45
+ bcc?: string | undefined;
46
+ thread_id?: string | undefined;
47
+ reply_to_email_id?: string | undefined;
48
+ from_draft_id?: string | undefined;
49
+ }>;
50
+ export declare function sendEmailTool(server: Server, clientFactory: ClientFactory): {
51
+ name: string;
52
+ description: string;
53
+ inputSchema: {
54
+ type: "object";
55
+ properties: {
56
+ to: {
57
+ type: string;
58
+ description: "Recipient email address(es). For multiple recipients, separate with commas.";
59
+ };
60
+ subject: {
61
+ type: string;
62
+ description: "Subject line of the email.";
63
+ };
64
+ body: {
65
+ type: string;
66
+ description: "Plain text body content of the email.";
67
+ };
68
+ cc: {
69
+ type: string;
70
+ description: "CC recipient email address(es). For multiple, separate with commas.";
71
+ };
72
+ bcc: {
73
+ type: string;
74
+ description: "BCC recipient email address(es). For multiple, separate with commas.";
75
+ };
76
+ thread_id: {
77
+ type: string;
78
+ description: string;
79
+ };
80
+ reply_to_email_id: {
81
+ type: string;
82
+ description: string;
83
+ };
84
+ from_draft_id: {
85
+ type: string;
86
+ description: string;
87
+ };
88
+ };
89
+ required: never[];
90
+ };
91
+ handler: (args: unknown) => Promise<{
92
+ content: {
93
+ type: string;
94
+ text: string;
95
+ }[];
96
+ isError?: undefined;
97
+ } | {
98
+ content: {
99
+ type: string;
100
+ text: string;
101
+ }[];
102
+ isError: boolean;
103
+ }>;
104
+ };
@@ -0,0 +1,181 @@
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 email to an existing conversation. ' +
10
+ 'Get this from get_email_conversation. If provided, the email will be a reply in that thread.',
11
+ reply_to_email_id: 'Email ID to reply to. If provided, the email will be formatted as a reply ' +
12
+ 'with proper In-Reply-To and References headers. Also requires thread_id.',
13
+ 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, body, etc.) are ignored.',
15
+ };
16
+ export const SendEmailSchema = z
17
+ .object({
18
+ to: z.string().optional().describe(PARAM_DESCRIPTIONS.to),
19
+ subject: z.string().optional().describe(PARAM_DESCRIPTIONS.subject),
20
+ body: z.string().optional().describe(PARAM_DESCRIPTIONS.body),
21
+ cc: z.string().optional().describe(PARAM_DESCRIPTIONS.cc),
22
+ bcc: z.string().optional().describe(PARAM_DESCRIPTIONS.bcc),
23
+ thread_id: z.string().optional().describe(PARAM_DESCRIPTIONS.thread_id),
24
+ reply_to_email_id: z.string().optional().describe(PARAM_DESCRIPTIONS.reply_to_email_id),
25
+ from_draft_id: z.string().optional().describe(PARAM_DESCRIPTIONS.from_draft_id),
26
+ })
27
+ .refine((data) => {
28
+ // Either from_draft_id is provided, OR to, subject, and body are all provided
29
+ if (data.from_draft_id) {
30
+ return true;
31
+ }
32
+ return data.to && data.subject && data.body;
33
+ }, {
34
+ message: 'Either provide from_draft_id to send a draft, or provide to, subject, and body to send a new email.',
35
+ });
36
+ const TOOL_DESCRIPTION = `Send an email immediately or send a previously created draft.
37
+
38
+ **Option 1: Send a new email**
39
+ - to: Recipient email address(es) (required)
40
+ - subject: Email subject line (required)
41
+ - body: Plain text body content (required)
42
+ - cc: CC recipients (optional)
43
+ - bcc: BCC recipients (optional)
44
+ - thread_id: Thread ID to reply to an existing conversation (optional)
45
+ - reply_to_email_id: Email ID to reply to, sets proper reply headers (optional)
46
+
47
+ **Option 2: Send a draft**
48
+ - from_draft_id: ID of the draft to send (all other parameters are ignored)
49
+
50
+ **Sending a reply:**
51
+ To send a reply to an existing email:
52
+ 1. Get the thread_id and email_id from get_email_conversation
53
+ 2. Provide both thread_id and reply_to_email_id parameters
54
+
55
+ **Use cases:**
56
+ - Send a new email immediately
57
+ - Reply to an existing email conversation
58
+ - Send a draft that was created with draft_email
59
+
60
+ **Warning:** This action sends the email immediately and cannot be undone.`;
61
+ export function sendEmailTool(server, clientFactory) {
62
+ return {
63
+ name: 'send_email',
64
+ description: TOOL_DESCRIPTION,
65
+ inputSchema: {
66
+ type: 'object',
67
+ properties: {
68
+ to: {
69
+ type: 'string',
70
+ description: PARAM_DESCRIPTIONS.to,
71
+ },
72
+ subject: {
73
+ type: 'string',
74
+ description: PARAM_DESCRIPTIONS.subject,
75
+ },
76
+ body: {
77
+ type: 'string',
78
+ description: PARAM_DESCRIPTIONS.body,
79
+ },
80
+ cc: {
81
+ type: 'string',
82
+ description: PARAM_DESCRIPTIONS.cc,
83
+ },
84
+ bcc: {
85
+ type: 'string',
86
+ description: PARAM_DESCRIPTIONS.bcc,
87
+ },
88
+ thread_id: {
89
+ type: 'string',
90
+ description: PARAM_DESCRIPTIONS.thread_id,
91
+ },
92
+ reply_to_email_id: {
93
+ type: 'string',
94
+ description: PARAM_DESCRIPTIONS.reply_to_email_id,
95
+ },
96
+ from_draft_id: {
97
+ type: 'string',
98
+ description: PARAM_DESCRIPTIONS.from_draft_id,
99
+ },
100
+ },
101
+ required: [],
102
+ },
103
+ handler: async (args) => {
104
+ try {
105
+ const parsed = SendEmailSchema.parse(args ?? {});
106
+ const client = clientFactory();
107
+ // Option 2: Send a draft
108
+ if (parsed.from_draft_id) {
109
+ const sentEmail = await client.sendDraft(parsed.from_draft_id);
110
+ return {
111
+ content: [
112
+ {
113
+ type: 'text',
114
+ text: `Draft sent successfully!\n\n**Message ID:** ${sentEmail.id}\n**Thread ID:** ${sentEmail.threadId}\n\nThe draft has been sent and removed from Drafts.`,
115
+ },
116
+ ],
117
+ };
118
+ }
119
+ // Option 1: Send a new email
120
+ // TypeScript knows these are defined due to the refine check
121
+ const to = parsed.to;
122
+ const subject = parsed.subject;
123
+ const body = parsed.body;
124
+ let inReplyTo;
125
+ let references;
126
+ // If replying to an email, get the Message-ID for proper threading
127
+ if (parsed.reply_to_email_id && parsed.thread_id) {
128
+ const originalEmail = await client.getMessage(parsed.reply_to_email_id, {
129
+ format: 'metadata',
130
+ metadataHeaders: ['Message-ID', 'References'],
131
+ });
132
+ const messageId = getHeader(originalEmail, 'Message-ID');
133
+ const originalReferences = getHeader(originalEmail, 'References');
134
+ if (messageId) {
135
+ inReplyTo = messageId;
136
+ // Build references chain
137
+ references = originalReferences ? `${originalReferences} ${messageId}` : messageId;
138
+ }
139
+ }
140
+ const sentEmail = await client.sendMessage({
141
+ to,
142
+ subject,
143
+ body,
144
+ cc: parsed.cc,
145
+ bcc: parsed.bcc,
146
+ threadId: parsed.thread_id,
147
+ inReplyTo,
148
+ references,
149
+ });
150
+ let responseText = `Email sent successfully!\n\n**Message ID:** ${sentEmail.id}\n**Thread ID:** ${sentEmail.threadId}`;
151
+ if (parsed.thread_id) {
152
+ responseText += '\n\nThis email was sent as a reply in an existing conversation.';
153
+ }
154
+ responseText += `\n\n**To:** ${to}`;
155
+ responseText += `\n**Subject:** ${subject}`;
156
+ if (parsed.cc) {
157
+ responseText += `\n**CC:** ${parsed.cc}`;
158
+ }
159
+ return {
160
+ content: [
161
+ {
162
+ type: 'text',
163
+ text: responseText,
164
+ },
165
+ ],
166
+ };
167
+ }
168
+ catch (error) {
169
+ return {
170
+ content: [
171
+ {
172
+ type: 'text',
173
+ text: `Error sending email: ${error instanceof Error ? error.message : 'Unknown error'}`,
174
+ },
175
+ ],
176
+ isError: true,
177
+ };
178
+ }
179
+ },
180
+ };
181
+ }
package/shared/tools.d.ts CHANGED
@@ -1,9 +1,26 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { ClientFactory } from './server.js';
3
3
  /**
4
- * Creates a function to register all tools with the server
4
+ * Available tool groups for Gmail MCP server
5
+ * - readonly: Read-only operations (list, get, search emails)
6
+ * - readwrite: Read and write operations (includes readonly + modify, draft)
7
+ * - readwrite_external: External communication operations (includes readwrite + send_email)
5
8
  */
6
- export declare function createRegisterTools(clientFactory: ClientFactory): (server: Server) => void;
9
+ export type ToolGroup = 'readonly' | 'readwrite' | 'readwrite_external';
10
+ /**
11
+ * Parses the ENABLED_TOOLGROUPS environment variable
12
+ * @param enabledGroupsParam - Comma-separated list of tool groups
13
+ * @returns Array of valid tool groups
14
+ */
15
+ export declare function parseEnabledToolGroups(enabledGroupsParam?: string): ToolGroup[];
16
+ /**
17
+ * Gets all available tool group names
18
+ */
19
+ export declare function getAvailableToolGroups(): ToolGroup[];
20
+ /**
21
+ * Creates a function to register tools with the server based on enabled groups
22
+ */
23
+ export declare function createRegisterTools(clientFactory: ClientFactory, enabledGroups?: ToolGroup[]): (server: Server) => void;
7
24
  /**
8
25
  * Backward compatibility export
9
26
  */
package/shared/tools.js CHANGED
@@ -1,17 +1,65 @@
1
1
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
2
- import { listRecentEmailsTool } from './tools/list-recent-emails.js';
3
- import { getEmailTool } from './tools/get-email.js';
2
+ import { listEmailConversationsTool } from './tools/list-email-conversations.js';
3
+ import { getEmailConversationTool } from './tools/get-email-conversation.js';
4
+ import { changeEmailConversationTool } from './tools/change-email-conversation.js';
5
+ import { draftEmailTool } from './tools/draft-email.js';
6
+ import { sendEmailTool } from './tools/send-email.js';
7
+ import { searchEmailConversationsTool } from './tools/search-email-conversations.js';
8
+ const ALL_TOOL_GROUPS = ['readonly', 'readwrite', 'readwrite_external'];
4
9
  /**
5
- * All available tools
10
+ * All available tools with their group assignments
11
+ *
12
+ * readonly: list_email_conversations, get_email_conversation, search_email_conversations
13
+ * readwrite: all readonly tools + change_email_conversation, draft_email
14
+ * readwrite_external: all readwrite tools + send_email (external communication)
6
15
  */
7
- const ALL_TOOLS = [listRecentEmailsTool, getEmailTool];
16
+ const ALL_TOOLS = [
17
+ // Read-only tools (available in all groups)
18
+ { factory: listEmailConversationsTool, groups: ['readonly', 'readwrite', 'readwrite_external'] },
19
+ { factory: getEmailConversationTool, groups: ['readonly', 'readwrite', 'readwrite_external'] },
20
+ {
21
+ factory: searchEmailConversationsTool,
22
+ groups: ['readonly', 'readwrite', 'readwrite_external'],
23
+ },
24
+ // Write tools (available in readwrite and readwrite_external)
25
+ { factory: changeEmailConversationTool, groups: ['readwrite', 'readwrite_external'] },
26
+ { factory: draftEmailTool, groups: ['readwrite', 'readwrite_external'] },
27
+ // External communication tools (only in readwrite_external - most dangerous)
28
+ { factory: sendEmailTool, groups: ['readwrite_external'] },
29
+ ];
8
30
  /**
9
- * Creates a function to register all tools with the server
31
+ * Parses the ENABLED_TOOLGROUPS environment variable
32
+ * @param enabledGroupsParam - Comma-separated list of tool groups
33
+ * @returns Array of valid tool groups
10
34
  */
11
- export function createRegisterTools(clientFactory) {
35
+ export function parseEnabledToolGroups(enabledGroupsParam) {
36
+ if (!enabledGroupsParam) {
37
+ return ALL_TOOL_GROUPS; // All groups enabled by default
38
+ }
39
+ const requestedGroups = enabledGroupsParam.split(',').map((g) => g.trim().toLowerCase());
40
+ const validGroups = requestedGroups.filter((g) => ALL_TOOL_GROUPS.includes(g));
41
+ if (validGroups.length === 0) {
42
+ console.error(`Warning: No valid tool groups found in "${enabledGroupsParam}". ` +
43
+ `Valid groups: ${ALL_TOOL_GROUPS.join(', ')}. Using all groups.`);
44
+ return ALL_TOOL_GROUPS;
45
+ }
46
+ return validGroups;
47
+ }
48
+ /**
49
+ * Gets all available tool group names
50
+ */
51
+ export function getAvailableToolGroups() {
52
+ return [...ALL_TOOL_GROUPS];
53
+ }
54
+ /**
55
+ * Creates a function to register tools with the server based on enabled groups
56
+ */
57
+ export function createRegisterTools(clientFactory, enabledGroups) {
58
+ // Parse enabled groups from environment or use provided array
59
+ const groups = enabledGroups || parseEnabledToolGroups(process.env.GMAIL_ENABLED_TOOLGROUPS);
12
60
  return (server) => {
13
- // Create tool instances
14
- const tools = ALL_TOOLS.map((factory) => factory(server, clientFactory));
61
+ // Filter tools by enabled groups and create instances
62
+ const tools = ALL_TOOLS.filter((def) => def.groups.some((g) => groups.includes(g))).map((def) => def.factory(server, clientFactory));
15
63
  // List available tools
16
64
  server.setRequestHandler(ListToolsRequestSchema, async () => {
17
65
  return {
@@ -3,3 +3,7 @@ import type { Email } from '../types.js';
3
3
  * Extracts a header value from an email by header name (case-insensitive)
4
4
  */
5
5
  export declare function getHeader(email: Email, headerName: string): string | undefined;
6
+ /**
7
+ * Formats an email for display in tool output
8
+ */
9
+ export declare function formatEmail(email: Email): string;
@@ -5,3 +5,18 @@ export function getHeader(email, headerName) {
5
5
  return email.payload?.headers?.find((h) => h.name.toLowerCase() === headerName.toLowerCase())
6
6
  ?.value;
7
7
  }
8
+ /**
9
+ * Formats an email for display in tool output
10
+ */
11
+ export function formatEmail(email) {
12
+ const subject = getHeader(email, 'Subject') || '(No Subject)';
13
+ const from = getHeader(email, 'From') || 'Unknown';
14
+ const date = getHeader(email, 'Date') || 'Unknown date';
15
+ const snippet = email.snippet || '';
16
+ return `**ID:** ${email.id}
17
+ **Thread ID:** ${email.threadId}
18
+ **Subject:** ${subject}
19
+ **From:** ${from}
20
+ **Date:** ${date}
21
+ **Preview:** ${snippet}`;
22
+ }
@@ -1,133 +0,0 @@
1
- import { z } from 'zod';
2
- import { getHeader } from '../utils/email-helpers.js';
3
- const PARAM_DESCRIPTIONS = {
4
- hours: 'Time horizon in hours to look back for emails. Default: 24. ' +
5
- 'Example: 48 for the last 2 days.',
6
- labels: 'Comma-separated list of label IDs to filter by. Default: INBOX. ' +
7
- 'Common labels: INBOX, SENT, DRAFTS, SPAM, TRASH, STARRED, IMPORTANT, UNREAD.',
8
- max_results: 'Maximum number of emails to return. Default: 10. Max: 100.',
9
- };
10
- export const ListRecentEmailsSchema = z.object({
11
- hours: z.number().positive().default(24).describe(PARAM_DESCRIPTIONS.hours),
12
- labels: z.string().optional().default('INBOX').describe(PARAM_DESCRIPTIONS.labels),
13
- max_results: z.number().positive().max(100).default(10).describe(PARAM_DESCRIPTIONS.max_results),
14
- });
15
- const TOOL_DESCRIPTION = `List recent emails from Gmail within a specified time horizon.
16
-
17
- Returns a list of recent emails with their subject, sender, date, and a snippet preview. Use get_email to retrieve the full content of a specific email.
18
-
19
- **Parameters:**
20
- - hours: How far back to look for emails (default: 24 hours)
21
- - labels: Which labels/folders to search (default: INBOX)
22
- - max_results: Maximum emails to return (default: 10, max: 100)
23
-
24
- **Returns:**
25
- A formatted list of emails with:
26
- - Email ID (needed for get_email)
27
- - Subject line
28
- - Sender (From)
29
- - Date received
30
- - Snippet preview
31
-
32
- **Use cases:**
33
- - Check recent inbox activity
34
- - Monitor for new emails in a time window
35
- - List recent emails from specific labels like SENT or STARRED
36
-
37
- **Note:** This tool only returns email metadata and snippets. Use get_email with an email ID to retrieve the full message content.`;
38
- /**
39
- * Formats an email for display
40
- */
41
- function formatEmail(email) {
42
- const subject = getHeader(email, 'Subject') || '(No Subject)';
43
- const from = getHeader(email, 'From') || 'Unknown';
44
- const date = getHeader(email, 'Date') || 'Unknown date';
45
- const snippet = email.snippet || '';
46
- return `**ID:** ${email.id}
47
- **Subject:** ${subject}
48
- **From:** ${from}
49
- **Date:** ${date}
50
- **Preview:** ${snippet}`;
51
- }
52
- export function listRecentEmailsTool(server, clientFactory) {
53
- return {
54
- name: 'gmail_list_recent_emails',
55
- description: TOOL_DESCRIPTION,
56
- inputSchema: {
57
- type: 'object',
58
- properties: {
59
- hours: {
60
- type: 'number',
61
- default: 24,
62
- description: PARAM_DESCRIPTIONS.hours,
63
- },
64
- labels: {
65
- type: 'string',
66
- default: 'INBOX',
67
- description: PARAM_DESCRIPTIONS.labels,
68
- },
69
- max_results: {
70
- type: 'number',
71
- default: 10,
72
- description: PARAM_DESCRIPTIONS.max_results,
73
- },
74
- },
75
- required: [],
76
- },
77
- handler: async (args) => {
78
- try {
79
- const parsed = ListRecentEmailsSchema.parse(args ?? {});
80
- const client = clientFactory();
81
- // Calculate the timestamp for the time horizon
82
- const now = new Date();
83
- const cutoffDate = new Date(now.getTime() - parsed.hours * 60 * 60 * 1000);
84
- const afterTimestamp = Math.floor(cutoffDate.getTime() / 1000);
85
- // Build the Gmail query
86
- const query = `after:${afterTimestamp}`;
87
- // Parse labels
88
- const labelIds = parsed.labels.split(',').map((l) => l.trim().toUpperCase());
89
- // List messages
90
- const { messages } = await client.listMessages({
91
- q: query,
92
- maxResults: parsed.max_results,
93
- labelIds,
94
- });
95
- if (messages.length === 0) {
96
- return {
97
- content: [
98
- {
99
- type: 'text',
100
- text: `No emails found in the last ${parsed.hours} hour(s) with labels: ${labelIds.join(', ')}`,
101
- },
102
- ],
103
- };
104
- }
105
- // Fetch full details for each message
106
- const emailDetails = await Promise.all(messages.map((msg) => client.getMessage(msg.id, {
107
- format: 'metadata',
108
- metadataHeaders: ['Subject', 'From', 'Date'],
109
- })));
110
- const formattedEmails = emailDetails.map(formatEmail).join('\n\n---\n\n');
111
- return {
112
- content: [
113
- {
114
- type: 'text',
115
- text: `Found ${messages.length} email(s) in the last ${parsed.hours} hour(s):\n\n${formattedEmails}`,
116
- },
117
- ],
118
- };
119
- }
120
- catch (error) {
121
- return {
122
- content: [
123
- {
124
- type: 'text',
125
- text: `Error listing emails: ${error instanceof Error ? error.message : 'Unknown error'}`,
126
- },
127
- ],
128
- isError: true,
129
- };
130
- }
131
- },
132
- };
133
- }