@littlebearapps/outlook-assistant 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +22 -0
- package/LICENSE +21 -0
- package/README.md +422 -0
- package/advanced/index.js +652 -0
- package/auth/index.js +32 -0
- package/auth/oauth-server.js +233 -0
- package/auth/token-manager.js +105 -0
- package/auth/token-storage.js +359 -0
- package/auth/tools.js +159 -0
- package/calendar/accept.js +72 -0
- package/calendar/cancel.js +72 -0
- package/calendar/create.js +115 -0
- package/calendar/decline.js +72 -0
- package/calendar/delete.js +67 -0
- package/calendar/index.js +130 -0
- package/calendar/list.js +108 -0
- package/categories/index.js +955 -0
- package/config.js +95 -0
- package/contacts/index.js +754 -0
- package/email/attachments.js +365 -0
- package/email/conversations.js +666 -0
- package/email/delta.js +210 -0
- package/email/export.js +572 -0
- package/email/folder-utils.js +192 -0
- package/email/headers.js +344 -0
- package/email/index.js +537 -0
- package/email/list.js +136 -0
- package/email/mark-as-read.js +114 -0
- package/email/mime.js +286 -0
- package/email/read.js +161 -0
- package/email/search.js +628 -0
- package/email/send.js +169 -0
- package/folder/create.js +137 -0
- package/folder/delete.js +108 -0
- package/folder/index.js +112 -0
- package/folder/list.js +289 -0
- package/folder/move.js +186 -0
- package/folder/stats.js +322 -0
- package/index.js +162 -0
- package/llms.txt +76 -0
- package/outlook-auth-server.js +384 -0
- package/package.json +97 -0
- package/rules/create.js +273 -0
- package/rules/index.js +276 -0
- package/rules/list.js +216 -0
- package/settings/index.js +678 -0
- package/utils/field-presets.js +311 -0
- package/utils/graph-api.js +268 -0
- package/utils/mock-data.js +154 -0
- package/utils/odata-helpers.js +33 -0
- package/utils/response-formatter.js +457 -0
- package/utils/safety.js +123 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mark email as read functionality
|
|
3
|
+
*/
|
|
4
|
+
const _config = require('../config'); // Reserved for future use
|
|
5
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
6
|
+
const { ensureAuthenticated } = require('../auth');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Mark email as read handler
|
|
10
|
+
* @param {object} args - Tool arguments
|
|
11
|
+
* @returns {object} - MCP response
|
|
12
|
+
*/
|
|
13
|
+
async function handleMarkAsRead(args) {
|
|
14
|
+
const emailId = args.id;
|
|
15
|
+
const isRead = args.isRead !== undefined ? args.isRead : true; // Default to true
|
|
16
|
+
|
|
17
|
+
if (!emailId) {
|
|
18
|
+
return {
|
|
19
|
+
content: [
|
|
20
|
+
{
|
|
21
|
+
type: 'text',
|
|
22
|
+
text: 'Email ID is required.',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Get access token
|
|
30
|
+
const accessToken = await ensureAuthenticated();
|
|
31
|
+
|
|
32
|
+
// Make API call to update email read status
|
|
33
|
+
const endpoint = `me/messages/${emailId}`;
|
|
34
|
+
const updateData = {
|
|
35
|
+
isRead: isRead,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const _result = await callGraphAPI(
|
|
40
|
+
accessToken,
|
|
41
|
+
'PATCH',
|
|
42
|
+
endpoint,
|
|
43
|
+
updateData
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const status = isRead ? 'read' : 'unread';
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: `Email successfully marked as ${status}.`,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(
|
|
58
|
+
`Error marking email as ${isRead ? 'read' : 'unread'}: ${error.message}`
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Improved error handling with more specific messages
|
|
62
|
+
if (error.message.includes("doesn't belong to the targeted mailbox")) {
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: 'text',
|
|
67
|
+
text: `The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
} else if (error.message.includes('UNAUTHORIZED')) {
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: 'Authentication failed. Please re-authenticate and try again.',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
} else {
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: 'text',
|
|
85
|
+
text: `Failed to mark email as ${isRead ? 'read' : 'unread'}: ${error.message}`,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error.message === 'Authentication required') {
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: 'text',
|
|
97
|
+
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: 'text',
|
|
107
|
+
text: `Error accessing email: ${error.message}`,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = handleMarkAsRead;
|
package/email/mime.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIME content retrieval functionality
|
|
3
|
+
*
|
|
4
|
+
* Retrieves raw MIME/EML content from emails for archival, forensics,
|
|
5
|
+
* and forwarding to other systems.
|
|
6
|
+
*/
|
|
7
|
+
const { callGraphAPIRaw } = require('../utils/graph-api');
|
|
8
|
+
const { ensureAuthenticated } = require('../auth');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse MIME headers from raw content
|
|
12
|
+
* @param {string} mimeContent - Raw MIME content
|
|
13
|
+
* @returns {object} - Parsed headers and body boundary info
|
|
14
|
+
*/
|
|
15
|
+
function parseMimeHeaders(mimeContent) {
|
|
16
|
+
// Find the first blank line (separates headers from body)
|
|
17
|
+
const headerEndIndex = mimeContent.indexOf('\r\n\r\n');
|
|
18
|
+
if (headerEndIndex === -1) {
|
|
19
|
+
return { headers: {}, headerSection: '', bodyStart: 0 };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const headerSection = mimeContent.substring(0, headerEndIndex);
|
|
23
|
+
const headers = {};
|
|
24
|
+
|
|
25
|
+
// Parse headers (handle folded headers)
|
|
26
|
+
const lines = headerSection.split(/\r?\n/);
|
|
27
|
+
let currentHeader = null;
|
|
28
|
+
let currentValue = '';
|
|
29
|
+
|
|
30
|
+
lines.forEach((line) => {
|
|
31
|
+
if (line.startsWith(' ') || line.startsWith('\t')) {
|
|
32
|
+
// Continuation of previous header
|
|
33
|
+
currentValue += ' ' + line.trim();
|
|
34
|
+
} else {
|
|
35
|
+
// Save previous header
|
|
36
|
+
if (currentHeader) {
|
|
37
|
+
headers[currentHeader] = currentValue;
|
|
38
|
+
}
|
|
39
|
+
// Start new header
|
|
40
|
+
const colonIndex = line.indexOf(':');
|
|
41
|
+
if (colonIndex !== -1) {
|
|
42
|
+
currentHeader = line.substring(0, colonIndex).trim();
|
|
43
|
+
currentValue = line.substring(colonIndex + 1).trim();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Save last header
|
|
49
|
+
if (currentHeader) {
|
|
50
|
+
headers[currentHeader] = currentValue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
headers,
|
|
55
|
+
headerSection,
|
|
56
|
+
bodyStart: headerEndIndex + 4,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get MIME content size info
|
|
62
|
+
* @param {string} mimeContent - Raw MIME content
|
|
63
|
+
* @returns {object} - Size information
|
|
64
|
+
*/
|
|
65
|
+
function getMimeStats(mimeContent) {
|
|
66
|
+
const bytes = Buffer.byteLength(mimeContent, 'utf8');
|
|
67
|
+
const lines = mimeContent.split(/\r?\n/).length;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
bytes,
|
|
71
|
+
formattedSize:
|
|
72
|
+
bytes < 1024
|
|
73
|
+
? `${bytes} B`
|
|
74
|
+
: bytes < 1024 * 1024
|
|
75
|
+
? `${(bytes / 1024).toFixed(1)} KB`
|
|
76
|
+
: `${(bytes / (1024 * 1024)).toFixed(2)} MB`,
|
|
77
|
+
lines,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get MIME content handler
|
|
83
|
+
* @param {object} args - Tool arguments
|
|
84
|
+
* @param {string} args.id - Email ID (required)
|
|
85
|
+
* @param {boolean} [args.headersOnly] - Return only MIME headers (default: false)
|
|
86
|
+
* @param {boolean} [args.base64] - Return content as base64 (for binary safety, default: false)
|
|
87
|
+
* @param {number} [args.maxSize] - Max content size to return in bytes (default: 1MB, 0 = no limit)
|
|
88
|
+
* @returns {object} - MCP response with MIME content
|
|
89
|
+
*/
|
|
90
|
+
async function handleGetMimeContent(args) {
|
|
91
|
+
const emailId = args.id;
|
|
92
|
+
const headersOnly = args.headersOnly || false;
|
|
93
|
+
const returnBase64 = args.base64 || false;
|
|
94
|
+
const maxSize = args.maxSize !== undefined ? args.maxSize : 1024 * 1024; // 1MB default
|
|
95
|
+
|
|
96
|
+
if (!emailId) {
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: 'Email ID is required.',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
// Get access token
|
|
109
|
+
const accessToken = await ensureAuthenticated();
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Fetch raw MIME content
|
|
113
|
+
const mimeContent = await callGraphAPIRaw(accessToken, emailId);
|
|
114
|
+
|
|
115
|
+
if (!mimeContent) {
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: 'text',
|
|
120
|
+
text: `Failed to retrieve MIME content for email ${emailId}.`,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const stats = getMimeStats(mimeContent);
|
|
127
|
+
const parsed = parseMimeHeaders(mimeContent);
|
|
128
|
+
|
|
129
|
+
// Check size limit
|
|
130
|
+
if (maxSize > 0 && stats.bytes > maxSize) {
|
|
131
|
+
if (headersOnly) {
|
|
132
|
+
// Return just headers if over limit
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{
|
|
136
|
+
type: 'text',
|
|
137
|
+
text:
|
|
138
|
+
`# MIME Content (Headers Only - Content Truncated)\n\n` +
|
|
139
|
+
`**Size**: ${stats.formattedSize} (exceeds ${maxSize} byte limit)\n` +
|
|
140
|
+
`**Lines**: ${stats.lines}\n\n` +
|
|
141
|
+
`## MIME Headers\n\n\`\`\`\n${parsed.headerSection}\n\`\`\``,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
_meta: {
|
|
145
|
+
emailId,
|
|
146
|
+
bytes: stats.bytes,
|
|
147
|
+
lines: stats.lines,
|
|
148
|
+
truncated: true,
|
|
149
|
+
headersOnly: true,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
content: [
|
|
156
|
+
{
|
|
157
|
+
type: 'text',
|
|
158
|
+
text:
|
|
159
|
+
`# MIME Content Too Large\n\n` +
|
|
160
|
+
`**Size**: ${stats.formattedSize}\n` +
|
|
161
|
+
`**Limit**: ${(maxSize / 1024).toFixed(0)} KB\n\n` +
|
|
162
|
+
`Use \`headersOnly: true\` to get just headers, or increase \`maxSize\` parameter.\n\n` +
|
|
163
|
+
`## MIME Headers Preview\n\n\`\`\`\n${parsed.headerSection.substring(0, 2000)}${parsed.headerSection.length > 2000 ? '\n...' : ''}\n\`\`\``,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
_meta: {
|
|
167
|
+
emailId,
|
|
168
|
+
bytes: stats.bytes,
|
|
169
|
+
lines: stats.lines,
|
|
170
|
+
truncated: true,
|
|
171
|
+
maxSizeExceeded: true,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Prepare content based on options
|
|
177
|
+
let content;
|
|
178
|
+
if (headersOnly) {
|
|
179
|
+
content = parsed.headerSection;
|
|
180
|
+
} else if (returnBase64) {
|
|
181
|
+
content = Buffer.from(mimeContent, 'utf8').toString('base64');
|
|
182
|
+
} else {
|
|
183
|
+
content = mimeContent;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Build response
|
|
187
|
+
let output = [];
|
|
188
|
+
output.push(`# MIME Content${headersOnly ? ' (Headers Only)' : ''}\n`);
|
|
189
|
+
output.push(`**Size**: ${stats.formattedSize}`);
|
|
190
|
+
output.push(`**Lines**: ${stats.lines}`);
|
|
191
|
+
|
|
192
|
+
// Show key headers
|
|
193
|
+
if (parsed.headers['Subject']) {
|
|
194
|
+
output.push(`**Subject**: ${parsed.headers['Subject']}`);
|
|
195
|
+
}
|
|
196
|
+
if (parsed.headers['From']) {
|
|
197
|
+
output.push(`**From**: ${parsed.headers['From']}`);
|
|
198
|
+
}
|
|
199
|
+
if (parsed.headers['Content-Type']) {
|
|
200
|
+
output.push(
|
|
201
|
+
`**Content-Type**: ${parsed.headers['Content-Type'].split(';')[0]}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
output.push('\n---\n');
|
|
206
|
+
|
|
207
|
+
if (returnBase64) {
|
|
208
|
+
output.push('## Base64 Encoded Content\n');
|
|
209
|
+
output.push('```');
|
|
210
|
+
output.push(content);
|
|
211
|
+
output.push('```');
|
|
212
|
+
} else {
|
|
213
|
+
output.push('## Raw MIME Content\n');
|
|
214
|
+
output.push('```');
|
|
215
|
+
output.push(content);
|
|
216
|
+
output.push('```');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
content: [
|
|
221
|
+
{
|
|
222
|
+
type: 'text',
|
|
223
|
+
text: output.join('\n'),
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
_meta: {
|
|
227
|
+
emailId,
|
|
228
|
+
bytes: stats.bytes,
|
|
229
|
+
lines: stats.lines,
|
|
230
|
+
format: returnBase64 ? 'base64' : 'raw',
|
|
231
|
+
headersOnly,
|
|
232
|
+
contentType: parsed.headers['Content-Type'] || 'unknown',
|
|
233
|
+
messageId: parsed.headers['Message-ID'] || null,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.error(`Error getting MIME content: ${error.message}`);
|
|
238
|
+
|
|
239
|
+
if (error.message.includes("doesn't belong to the targeted mailbox")) {
|
|
240
|
+
return {
|
|
241
|
+
content: [
|
|
242
|
+
{
|
|
243
|
+
type: 'text',
|
|
244
|
+
text: `The email ID seems invalid or doesn't belong to your mailbox.`,
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
content: [
|
|
252
|
+
{
|
|
253
|
+
type: 'text',
|
|
254
|
+
text: `Failed to get MIME content: ${error.message}`,
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error.message === 'Authentication required') {
|
|
261
|
+
return {
|
|
262
|
+
content: [
|
|
263
|
+
{
|
|
264
|
+
type: 'text',
|
|
265
|
+
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
content: [
|
|
273
|
+
{
|
|
274
|
+
type: 'text',
|
|
275
|
+
text: `Error accessing email: ${error.message}`,
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = {
|
|
283
|
+
handleGetMimeContent,
|
|
284
|
+
parseMimeHeaders,
|
|
285
|
+
getMimeStats,
|
|
286
|
+
};
|
package/email/read.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read email functionality
|
|
3
|
+
*
|
|
4
|
+
* Token-efficient implementation with outputVerbosity support and Markdown formatting.
|
|
5
|
+
*/
|
|
6
|
+
const _config = require('../config'); // Reserved for future use
|
|
7
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
8
|
+
const { ensureAuthenticated } = require('../auth');
|
|
9
|
+
const {
|
|
10
|
+
formatEmailContent,
|
|
11
|
+
VERBOSITY,
|
|
12
|
+
} = require('../utils/response-formatter');
|
|
13
|
+
const { getEmailFields } = require('../utils/field-presets');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get field preset based on verbosity and options
|
|
17
|
+
* @param {string} verbosity - Verbosity level
|
|
18
|
+
* @param {boolean} includeHeaders - Whether headers are requested
|
|
19
|
+
* @returns {string} - Field preset name
|
|
20
|
+
*/
|
|
21
|
+
function getReadFieldPreset(verbosity, includeHeaders) {
|
|
22
|
+
if (includeHeaders) {
|
|
23
|
+
return 'forensic'; // Includes internetMessageHeaders
|
|
24
|
+
}
|
|
25
|
+
switch (verbosity) {
|
|
26
|
+
case VERBOSITY.MINIMAL:
|
|
27
|
+
return 'read-minimal'; // Includes bodyPreview + toRecipients
|
|
28
|
+
case VERBOSITY.FULL:
|
|
29
|
+
return 'read'; // Full read fields
|
|
30
|
+
case VERBOSITY.STANDARD:
|
|
31
|
+
default:
|
|
32
|
+
return 'read'; // Standard uses full read fields
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read email handler
|
|
38
|
+
* @param {object} args - Tool arguments
|
|
39
|
+
* @param {string} args.id - Email ID (required)
|
|
40
|
+
* @param {string} [args.outputVerbosity] - minimal, standard, or full (default: standard)
|
|
41
|
+
* @param {boolean} [args.includeHeaders] - Include email headers for legal/forensic use
|
|
42
|
+
* @returns {object} - MCP response with Markdown formatted content
|
|
43
|
+
*/
|
|
44
|
+
async function handleReadEmail(args) {
|
|
45
|
+
const emailId = args.id;
|
|
46
|
+
const verbosity = args.outputVerbosity || VERBOSITY.STANDARD;
|
|
47
|
+
const includeHeaders = args.includeHeaders || false;
|
|
48
|
+
|
|
49
|
+
if (!emailId) {
|
|
50
|
+
return {
|
|
51
|
+
content: [
|
|
52
|
+
{
|
|
53
|
+
type: 'text',
|
|
54
|
+
text: 'Email ID is required.',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Get access token
|
|
62
|
+
const accessToken = await ensureAuthenticated();
|
|
63
|
+
|
|
64
|
+
// Select fields based on verbosity and options
|
|
65
|
+
const fieldPreset = getReadFieldPreset(verbosity, includeHeaders);
|
|
66
|
+
const selectFields = getEmailFields(fieldPreset);
|
|
67
|
+
|
|
68
|
+
// Make API call to get email details
|
|
69
|
+
const endpoint = `me/messages/${emailId}`;
|
|
70
|
+
const queryParams = {
|
|
71
|
+
$select: selectFields,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const email = await callGraphAPI(
|
|
76
|
+
accessToken,
|
|
77
|
+
'GET',
|
|
78
|
+
endpoint,
|
|
79
|
+
null,
|
|
80
|
+
queryParams
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (!email) {
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: 'text',
|
|
88
|
+
text: `Email with ID ${emailId} not found.`,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Format using shared formatter (returns Markdown)
|
|
95
|
+
const formattedOutput = formatEmailContent(email, verbosity, {
|
|
96
|
+
includeHeaders: includeHeaders,
|
|
97
|
+
includeAllHeaders: false, // Only important headers by default
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: 'text',
|
|
104
|
+
text: formattedOutput,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
_meta: {
|
|
108
|
+
emailId: email.id,
|
|
109
|
+
conversationId: email.conversationId,
|
|
110
|
+
internetMessageId: email.internetMessageId,
|
|
111
|
+
verbosity: verbosity,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error(`Error reading email: ${error.message}`);
|
|
116
|
+
|
|
117
|
+
// Improved error handling with more specific messages
|
|
118
|
+
if (error.message.includes("doesn't belong to the targeted mailbox")) {
|
|
119
|
+
return {
|
|
120
|
+
content: [
|
|
121
|
+
{
|
|
122
|
+
type: 'text',
|
|
123
|
+
text: `The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`,
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
} else {
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: `Failed to read email: ${error.message}`,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (error.message === 'Authentication required') {
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: 'text',
|
|
144
|
+
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{
|
|
153
|
+
type: 'text',
|
|
154
|
+
text: `Error accessing email: ${error.message}`,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = handleReadEmail;
|