outlook-cli 1.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/CLI.md +89 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +104 -0
- package/README.md +994 -0
- package/auth/index.js +36 -0
- package/auth/oauth-server.js +139 -0
- package/auth/token-manager.js +199 -0
- package/auth/token-storage.js +282 -0
- package/auth/tools.js +127 -0
- package/calendar/accept.js +64 -0
- package/calendar/cancel.js +64 -0
- package/calendar/create.js +69 -0
- package/calendar/decline.js +64 -0
- package/calendar/delete.js +59 -0
- package/calendar/index.js +123 -0
- package/calendar/list.js +77 -0
- package/cli.js +1349 -0
- package/config.js +84 -0
- package/docs/PROJECT-STRUCTURE.md +52 -0
- package/docs/PUBLISHING.md +86 -0
- package/docs/REFERENCE.md +679 -0
- package/email/folder-utils.js +171 -0
- package/email/index.js +157 -0
- package/email/list.js +89 -0
- package/email/mark-as-read.js +101 -0
- package/email/read.js +128 -0
- package/email/search.js +282 -0
- package/email/send.js +120 -0
- package/folder/create.js +124 -0
- package/folder/index.js +78 -0
- package/folder/list.js +264 -0
- package/folder/move.js +163 -0
- package/index.js +136 -0
- package/outlook-auth-server.js +305 -0
- package/package.json +76 -0
- package/rules/create.js +248 -0
- package/rules/index.js +177 -0
- package/rules/list.js +202 -0
- package/tool-registry.js +54 -0
- package/utils/graph-api.js +120 -0
- package/utils/mock-data.js +145 -0
- package/utils/odata-helpers.js +40 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email folder utilities
|
|
3
|
+
*/
|
|
4
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cache of folder information to reduce API calls
|
|
8
|
+
* Format: { userId: { folderName: { id, path } } }
|
|
9
|
+
*/
|
|
10
|
+
const folderCache = {};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a folder name to its endpoint path
|
|
14
|
+
* @param {string} accessToken - Access token
|
|
15
|
+
* @param {string} folderName - Folder name to resolve
|
|
16
|
+
* @returns {Promise<string>} - Resolved endpoint path
|
|
17
|
+
*/
|
|
18
|
+
async function resolveFolderPath(accessToken, folderName) {
|
|
19
|
+
// Default to inbox if no folder specified
|
|
20
|
+
if (!folderName) {
|
|
21
|
+
return 'me/messages';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Handle well-known folder names
|
|
25
|
+
const wellKnownFolders = {
|
|
26
|
+
'inbox': 'me/messages',
|
|
27
|
+
'drafts': 'me/mailFolders/drafts/messages',
|
|
28
|
+
'sent': 'me/mailFolders/sentItems/messages',
|
|
29
|
+
'deleted': 'me/mailFolders/deletedItems/messages',
|
|
30
|
+
'junk': 'me/mailFolders/junkemail/messages',
|
|
31
|
+
'archive': 'me/mailFolders/archive/messages'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Check if it's a well-known folder (case-insensitive)
|
|
35
|
+
const lowerFolderName = folderName.toLowerCase();
|
|
36
|
+
if (wellKnownFolders[lowerFolderName]) {
|
|
37
|
+
console.error(`Using well-known folder path for "${folderName}"`);
|
|
38
|
+
return wellKnownFolders[lowerFolderName];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Try to find the folder by name
|
|
43
|
+
const folderId = await getFolderIdByName(accessToken, folderName);
|
|
44
|
+
if (folderId) {
|
|
45
|
+
const path = `me/mailFolders/${folderId}/messages`;
|
|
46
|
+
console.error(`Resolved folder "${folderName}" to path: ${path}`);
|
|
47
|
+
return path;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If not found, fall back to inbox
|
|
51
|
+
console.error(`Couldn't find folder "${folderName}", falling back to inbox`);
|
|
52
|
+
return 'me/messages';
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(`Error resolving folder "${folderName}": ${error.message}`);
|
|
55
|
+
return 'me/messages';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get the ID of a mail folder by its name
|
|
61
|
+
* @param {string} accessToken - Access token
|
|
62
|
+
* @param {string} folderName - Name of the folder to find
|
|
63
|
+
* @returns {Promise<string|null>} - Folder ID or null if not found
|
|
64
|
+
*/
|
|
65
|
+
async function getFolderIdByName(accessToken, folderName) {
|
|
66
|
+
try {
|
|
67
|
+
// First try with exact match filter
|
|
68
|
+
console.error(`Looking for folder with name "${folderName}"`);
|
|
69
|
+
const response = await callGraphAPI(
|
|
70
|
+
accessToken,
|
|
71
|
+
'GET',
|
|
72
|
+
'me/mailFolders',
|
|
73
|
+
null,
|
|
74
|
+
{ $filter: `displayName eq '${folderName}'` }
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (response.value && response.value.length > 0) {
|
|
78
|
+
console.error(`Found folder "${folderName}" with ID: ${response.value[0].id}`);
|
|
79
|
+
return response.value[0].id;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// If exact match fails, try to get all folders and do a case-insensitive comparison
|
|
83
|
+
console.error(`No exact match found for "${folderName}", trying case-insensitive search`);
|
|
84
|
+
const allFoldersResponse = await callGraphAPI(
|
|
85
|
+
accessToken,
|
|
86
|
+
'GET',
|
|
87
|
+
'me/mailFolders',
|
|
88
|
+
null,
|
|
89
|
+
{ $top: 100 }
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (allFoldersResponse.value) {
|
|
93
|
+
const lowerFolderName = folderName.toLowerCase();
|
|
94
|
+
const matchingFolder = allFoldersResponse.value.find(
|
|
95
|
+
folder => folder.displayName.toLowerCase() === lowerFolderName
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (matchingFolder) {
|
|
99
|
+
console.error(`Found case-insensitive match for "${folderName}" with ID: ${matchingFolder.id}`);
|
|
100
|
+
return matchingFolder.id;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.error(`No folder found matching "${folderName}"`);
|
|
105
|
+
return null;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(`Error finding folder "${folderName}": ${error.message}`);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get all mail folders
|
|
114
|
+
* @param {string} accessToken - Access token
|
|
115
|
+
* @returns {Promise<Array>} - Array of folder objects
|
|
116
|
+
*/
|
|
117
|
+
async function getAllFolders(accessToken) {
|
|
118
|
+
try {
|
|
119
|
+
// Get top-level folders
|
|
120
|
+
const response = await callGraphAPI(
|
|
121
|
+
accessToken,
|
|
122
|
+
'GET',
|
|
123
|
+
'me/mailFolders',
|
|
124
|
+
null,
|
|
125
|
+
{
|
|
126
|
+
$top: 100,
|
|
127
|
+
$select: 'id,displayName,parentFolderId,childFolderCount,totalItemCount,unreadItemCount'
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (!response.value) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Get child folders for folders with children
|
|
136
|
+
const foldersWithChildren = response.value.filter(f => f.childFolderCount > 0);
|
|
137
|
+
|
|
138
|
+
const childFolderPromises = foldersWithChildren.map(async (folder) => {
|
|
139
|
+
try {
|
|
140
|
+
const childResponse = await callGraphAPI(
|
|
141
|
+
accessToken,
|
|
142
|
+
'GET',
|
|
143
|
+
`me/mailFolders/${folder.id}/childFolders`,
|
|
144
|
+
null,
|
|
145
|
+
{
|
|
146
|
+
$select: 'id,displayName,parentFolderId,childFolderCount,totalItemCount,unreadItemCount'
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return childResponse.value || [];
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error(`Error getting child folders for "${folder.displayName}": ${error.message}`);
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const childFolders = await Promise.all(childFolderPromises);
|
|
158
|
+
|
|
159
|
+
// Combine top-level folders and all child folders
|
|
160
|
+
return [...response.value, ...childFolders.flat()];
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error(`Error getting all folders: ${error.message}`);
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = {
|
|
168
|
+
resolveFolderPath,
|
|
169
|
+
getFolderIdByName,
|
|
170
|
+
getAllFolders
|
|
171
|
+
};
|
package/email/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email module for Outlook MCP server
|
|
3
|
+
*/
|
|
4
|
+
const handleListEmails = require('./list');
|
|
5
|
+
const handleSearchEmails = require('./search');
|
|
6
|
+
const handleReadEmail = require('./read');
|
|
7
|
+
const handleSendEmail = require('./send');
|
|
8
|
+
const handleMarkAsRead = require('./mark-as-read');
|
|
9
|
+
|
|
10
|
+
// Email tool definitions
|
|
11
|
+
const emailTools = [
|
|
12
|
+
{
|
|
13
|
+
name: "list-emails",
|
|
14
|
+
description: "Lists recent emails from your inbox",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
folder: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Email folder to list (e.g., 'inbox', 'sent', 'drafts', default: 'inbox')"
|
|
21
|
+
},
|
|
22
|
+
count: {
|
|
23
|
+
type: "number",
|
|
24
|
+
description: "Number of emails to retrieve (default: 10, max: 50)"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
required: []
|
|
28
|
+
},
|
|
29
|
+
handler: handleListEmails
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "search-emails",
|
|
33
|
+
description: "Search for emails using various criteria",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
query: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Search query text to find in emails"
|
|
40
|
+
},
|
|
41
|
+
folder: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "Email folder to search in (default: 'inbox')"
|
|
44
|
+
},
|
|
45
|
+
from: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "Filter by sender email address or name"
|
|
48
|
+
},
|
|
49
|
+
to: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Filter by recipient email address or name"
|
|
52
|
+
},
|
|
53
|
+
subject: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "Filter by email subject"
|
|
56
|
+
},
|
|
57
|
+
hasAttachments: {
|
|
58
|
+
type: "boolean",
|
|
59
|
+
description: "Filter to only emails with attachments"
|
|
60
|
+
},
|
|
61
|
+
unreadOnly: {
|
|
62
|
+
type: "boolean",
|
|
63
|
+
description: "Filter to only unread emails"
|
|
64
|
+
},
|
|
65
|
+
count: {
|
|
66
|
+
type: "number",
|
|
67
|
+
description: "Number of results to return (default: 10, max: 50)"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
required: []
|
|
71
|
+
},
|
|
72
|
+
handler: handleSearchEmails
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "read-email",
|
|
76
|
+
description: "Reads the content of a specific email",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
id: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "ID of the email to read"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
required: ["id"]
|
|
86
|
+
},
|
|
87
|
+
handler: handleReadEmail
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "send-email",
|
|
91
|
+
description: "Composes and sends a new email",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
to: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Comma-separated list of recipient email addresses"
|
|
98
|
+
},
|
|
99
|
+
cc: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "Comma-separated list of CC recipient email addresses"
|
|
102
|
+
},
|
|
103
|
+
bcc: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "Comma-separated list of BCC recipient email addresses"
|
|
106
|
+
},
|
|
107
|
+
subject: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "Email subject"
|
|
110
|
+
},
|
|
111
|
+
body: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: "Email body content (can be plain text or HTML)"
|
|
114
|
+
},
|
|
115
|
+
importance: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "Email importance (normal, high, low)",
|
|
118
|
+
enum: ["normal", "high", "low"]
|
|
119
|
+
},
|
|
120
|
+
saveToSentItems: {
|
|
121
|
+
type: "boolean",
|
|
122
|
+
description: "Whether to save the email to sent items"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
required: ["to", "subject", "body"]
|
|
126
|
+
},
|
|
127
|
+
handler: handleSendEmail
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "mark-as-read",
|
|
131
|
+
description: "Marks an email as read or unread",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
id: {
|
|
136
|
+
type: "string",
|
|
137
|
+
description: "ID of the email to mark as read/unread"
|
|
138
|
+
},
|
|
139
|
+
isRead: {
|
|
140
|
+
type: "boolean",
|
|
141
|
+
description: "Whether to mark as read (true) or unread (false). Default: true"
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
required: ["id"]
|
|
145
|
+
},
|
|
146
|
+
handler: handleMarkAsRead
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
emailTools,
|
|
152
|
+
handleListEmails,
|
|
153
|
+
handleSearchEmails,
|
|
154
|
+
handleReadEmail,
|
|
155
|
+
handleSendEmail,
|
|
156
|
+
handleMarkAsRead
|
|
157
|
+
};
|
package/email/list.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List emails functionality
|
|
3
|
+
*/
|
|
4
|
+
const config = require('../config');
|
|
5
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
6
|
+
const { ensureAuthenticated } = require('../auth');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* List emails handler
|
|
10
|
+
* @param {object} args - Tool arguments
|
|
11
|
+
* @returns {object} - MCP response
|
|
12
|
+
*/
|
|
13
|
+
async function handleListEmails(args) {
|
|
14
|
+
const folder = args.folder || "inbox";
|
|
15
|
+
const count = Math.min(args.count || 10, config.MAX_RESULT_COUNT);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Get access token
|
|
19
|
+
const accessToken = await ensureAuthenticated();
|
|
20
|
+
|
|
21
|
+
// Build API endpoint
|
|
22
|
+
let endpoint = 'me/messages';
|
|
23
|
+
if (folder.toLowerCase() !== 'inbox') {
|
|
24
|
+
// Get folder ID first if not inbox
|
|
25
|
+
const folderResponse = await callGraphAPI(
|
|
26
|
+
accessToken,
|
|
27
|
+
'GET',
|
|
28
|
+
`me/mailFolders?$filter=displayName eq '${folder}'`
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (folderResponse.value && folderResponse.value.length > 0) {
|
|
32
|
+
endpoint = `me/mailFolders/${folderResponse.value[0].id}/messages`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Add query parameters
|
|
37
|
+
const queryParams = {
|
|
38
|
+
$top: count,
|
|
39
|
+
$orderby: 'receivedDateTime desc',
|
|
40
|
+
$select: config.EMAIL_SELECT_FIELDS
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Make API call
|
|
44
|
+
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, queryParams);
|
|
45
|
+
|
|
46
|
+
if (!response.value || response.value.length === 0) {
|
|
47
|
+
return {
|
|
48
|
+
content: [{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: `No emails found in ${folder}.`
|
|
51
|
+
}]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Format results
|
|
56
|
+
const emailList = response.value.map((email, index) => {
|
|
57
|
+
const sender = email.from ? email.from.emailAddress : { name: 'Unknown', address: 'unknown' };
|
|
58
|
+
const date = new Date(email.receivedDateTime).toLocaleString();
|
|
59
|
+
const readStatus = email.isRead ? '' : '[UNREAD] ';
|
|
60
|
+
|
|
61
|
+
return `${index + 1}. ${readStatus}${date} - From: ${sender.name} (${sender.address})\nSubject: ${email.subject}\nID: ${email.id}\n`;
|
|
62
|
+
}).join("\n");
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
content: [{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: `Found ${response.value.length} emails in ${folder}:\n\n${emailList}`
|
|
68
|
+
}]
|
|
69
|
+
};
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error.message === 'Authentication required') {
|
|
72
|
+
return {
|
|
73
|
+
content: [{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
76
|
+
}]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
content: [{
|
|
82
|
+
type: "text",
|
|
83
|
+
text: `Error listing emails: ${error.message}`
|
|
84
|
+
}]
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = handleListEmails;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mark email as read functionality
|
|
3
|
+
*/
|
|
4
|
+
const config = require('../config');
|
|
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
|
+
type: "text",
|
|
21
|
+
text: "Email ID is required."
|
|
22
|
+
}]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Get access token
|
|
28
|
+
const accessToken = await ensureAuthenticated();
|
|
29
|
+
|
|
30
|
+
// Make API call to update email read status
|
|
31
|
+
const endpoint = `me/messages/${encodeURIComponent(emailId)}`;
|
|
32
|
+
const updateData = {
|
|
33
|
+
isRead: isRead
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const result = await callGraphAPI(accessToken, 'PATCH', endpoint, updateData);
|
|
38
|
+
|
|
39
|
+
const status = isRead ? 'read' : 'unread';
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: `Email successfully marked as ${status}.`
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
};
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`Error marking email as ${isRead ? 'read' : 'unread'}: ${error.message}`);
|
|
51
|
+
|
|
52
|
+
// Improved error handling with more specific messages
|
|
53
|
+
if (error.message.includes("doesn't belong to the targeted mailbox")) {
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: `The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
};
|
|
62
|
+
} else if (error.message.includes("UNAUTHORIZED")) {
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: "Authentication failed. Please re-authenticate and try again."
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
} else {
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: "text",
|
|
76
|
+
text: `Failed to mark email as ${isRead ? 'read' : 'unread'}: ${error.message}`
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error.message === 'Authentication required') {
|
|
84
|
+
return {
|
|
85
|
+
content: [{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
88
|
+
}]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
content: [{
|
|
94
|
+
type: "text",
|
|
95
|
+
text: `Error accessing email: ${error.message}`
|
|
96
|
+
}]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = handleMarkAsRead;
|
package/email/read.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read email functionality
|
|
3
|
+
*/
|
|
4
|
+
const config = require('../config');
|
|
5
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
6
|
+
const { ensureAuthenticated } = require('../auth');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read email handler
|
|
10
|
+
* @param {object} args - Tool arguments
|
|
11
|
+
* @returns {object} - MCP response
|
|
12
|
+
*/
|
|
13
|
+
async function handleReadEmail(args) {
|
|
14
|
+
const emailId = args.id;
|
|
15
|
+
|
|
16
|
+
if (!emailId) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: "Email ID is required."
|
|
21
|
+
}]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Get access token
|
|
27
|
+
const accessToken = await ensureAuthenticated();
|
|
28
|
+
|
|
29
|
+
// Make API call to get email details
|
|
30
|
+
const endpoint = `me/messages/${encodeURIComponent(emailId)}`;
|
|
31
|
+
const queryParams = {
|
|
32
|
+
$select: config.EMAIL_DETAIL_FIELDS
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const email = await callGraphAPI(accessToken, 'GET', endpoint, null, queryParams);
|
|
37
|
+
|
|
38
|
+
if (!email) {
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: `Email with ID ${emailId} not found.`
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Format sender, recipients, etc.
|
|
50
|
+
const sender = email.from ? `${email.from.emailAddress.name} (${email.from.emailAddress.address})` : 'Unknown';
|
|
51
|
+
const to = email.toRecipients ? email.toRecipients.map(r => `${r.emailAddress.name} (${r.emailAddress.address})`).join(", ") : 'None';
|
|
52
|
+
const cc = email.ccRecipients && email.ccRecipients.length > 0 ? email.ccRecipients.map(r => `${r.emailAddress.name} (${r.emailAddress.address})`).join(", ") : 'None';
|
|
53
|
+
const bcc = email.bccRecipients && email.bccRecipients.length > 0 ? email.bccRecipients.map(r => `${r.emailAddress.name} (${r.emailAddress.address})`).join(", ") : 'None';
|
|
54
|
+
const date = new Date(email.receivedDateTime).toLocaleString();
|
|
55
|
+
|
|
56
|
+
// Extract body content
|
|
57
|
+
let body = '';
|
|
58
|
+
if (email.body) {
|
|
59
|
+
body = email.body.contentType === 'html' ?
|
|
60
|
+
// Simple HTML-to-text conversion for HTML bodies
|
|
61
|
+
email.body.content.replace(/<[^>]*>/g, '') :
|
|
62
|
+
email.body.content;
|
|
63
|
+
} else {
|
|
64
|
+
body = email.bodyPreview || 'No content';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Format the email
|
|
68
|
+
const formattedEmail = `From: ${sender}
|
|
69
|
+
To: ${to}
|
|
70
|
+
${cc !== 'None' ? `CC: ${cc}\n` : ''}${bcc !== 'None' ? `BCC: ${bcc}\n` : ''}Subject: ${email.subject}
|
|
71
|
+
Date: ${date}
|
|
72
|
+
Importance: ${email.importance || 'normal'}
|
|
73
|
+
Has Attachments: ${email.hasAttachments ? 'Yes' : 'No'}
|
|
74
|
+
|
|
75
|
+
${body}`;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: formattedEmail
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(`Error reading email: ${error.message}`);
|
|
87
|
+
|
|
88
|
+
// Improved error handling with more specific messages
|
|
89
|
+
if (error.message.includes("doesn't belong to the targeted mailbox")) {
|
|
90
|
+
return {
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: `The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
};
|
|
98
|
+
} else {
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: `Failed to read email: ${error.message}`
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error.message === 'Authentication required') {
|
|
111
|
+
return {
|
|
112
|
+
content: [{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
115
|
+
}]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
content: [{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: `Error accessing email: ${error.message}`
|
|
123
|
+
}]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = handleReadEmail;
|