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
package/email/search.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Improved search emails functionality
|
|
3
|
+
*/
|
|
4
|
+
const config = require('../config');
|
|
5
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
6
|
+
const { ensureAuthenticated } = require('../auth');
|
|
7
|
+
const { resolveFolderPath } = require('./folder-utils');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Search emails handler
|
|
11
|
+
* @param {object} args - Tool arguments
|
|
12
|
+
* @returns {object} - MCP response
|
|
13
|
+
*/
|
|
14
|
+
async function handleSearchEmails(args) {
|
|
15
|
+
const folder = args.folder || "inbox";
|
|
16
|
+
const count = Math.min(args.count || 10, config.MAX_RESULT_COUNT);
|
|
17
|
+
const query = args.query || '';
|
|
18
|
+
const from = args.from || '';
|
|
19
|
+
const to = args.to || '';
|
|
20
|
+
const subject = args.subject || '';
|
|
21
|
+
const hasAttachments = args.hasAttachments;
|
|
22
|
+
const unreadOnly = args.unreadOnly;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Get access token
|
|
26
|
+
const accessToken = await ensureAuthenticated();
|
|
27
|
+
|
|
28
|
+
// Resolve the folder path
|
|
29
|
+
const endpoint = await resolveFolderPath(accessToken, folder);
|
|
30
|
+
console.error(`Using endpoint: ${endpoint} for folder: ${folder}`);
|
|
31
|
+
|
|
32
|
+
// Execute progressive search
|
|
33
|
+
const response = await progressiveSearch(
|
|
34
|
+
endpoint,
|
|
35
|
+
accessToken,
|
|
36
|
+
{ query, from, to, subject },
|
|
37
|
+
{ hasAttachments, unreadOnly },
|
|
38
|
+
count
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return formatSearchResults(response);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Handle authentication errors
|
|
44
|
+
if (error.message === 'Authentication required') {
|
|
45
|
+
return {
|
|
46
|
+
content: [{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
49
|
+
}]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// General error response
|
|
54
|
+
return {
|
|
55
|
+
content: [{
|
|
56
|
+
type: "text",
|
|
57
|
+
text: `Error searching emails: ${error.message}`
|
|
58
|
+
}]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Execute a search with progressively simpler fallback strategies
|
|
65
|
+
* @param {string} endpoint - API endpoint
|
|
66
|
+
* @param {string} accessToken - Access token
|
|
67
|
+
* @param {object} searchTerms - Search terms (query, from, to, subject)
|
|
68
|
+
* @param {object} filterTerms - Filter terms (hasAttachments, unreadOnly)
|
|
69
|
+
* @param {number} count - Maximum number of results
|
|
70
|
+
* @returns {Promise<object>} - Search results
|
|
71
|
+
*/
|
|
72
|
+
async function progressiveSearch(endpoint, accessToken, searchTerms, filterTerms, count) {
|
|
73
|
+
// Track search strategies attempted
|
|
74
|
+
const searchAttempts = [];
|
|
75
|
+
|
|
76
|
+
// 1. Try combined search (most specific)
|
|
77
|
+
try {
|
|
78
|
+
const params = buildSearchParams(searchTerms, filterTerms, count);
|
|
79
|
+
console.error("Attempting combined search with params:", params);
|
|
80
|
+
searchAttempts.push("combined-search");
|
|
81
|
+
|
|
82
|
+
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, params);
|
|
83
|
+
if (response.value && response.value.length > 0) {
|
|
84
|
+
console.error(`Combined search successful: found ${response.value.length} results`);
|
|
85
|
+
return response;
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`Combined search failed: ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 2. Try each search term individually, starting with most specific
|
|
92
|
+
const searchPriority = ['subject', 'from', 'to', 'query'];
|
|
93
|
+
|
|
94
|
+
for (const term of searchPriority) {
|
|
95
|
+
if (searchTerms[term]) {
|
|
96
|
+
try {
|
|
97
|
+
console.error(`Attempting search with only ${term}: "${searchTerms[term]}"`);
|
|
98
|
+
searchAttempts.push(`single-term-${term}`);
|
|
99
|
+
|
|
100
|
+
// For single term search, only use $search with that term
|
|
101
|
+
const simplifiedParams = {
|
|
102
|
+
$top: count,
|
|
103
|
+
$select: config.EMAIL_SELECT_FIELDS,
|
|
104
|
+
$orderby: 'receivedDateTime desc'
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Add the search term in the appropriate KQL syntax
|
|
108
|
+
if (term === 'query') {
|
|
109
|
+
// General query doesn't need a prefix
|
|
110
|
+
simplifiedParams.$search = `"${searchTerms[term]}"`;
|
|
111
|
+
} else {
|
|
112
|
+
// Specific field searches use field:value syntax
|
|
113
|
+
simplifiedParams.$search = `${term}:"${searchTerms[term]}"`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Add boolean filters if applicable
|
|
117
|
+
addBooleanFilters(simplifiedParams, filterTerms);
|
|
118
|
+
|
|
119
|
+
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, simplifiedParams);
|
|
120
|
+
if (response.value && response.value.length > 0) {
|
|
121
|
+
console.error(`Search with ${term} successful: found ${response.value.length} results`);
|
|
122
|
+
return response;
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error(`Search with ${term} failed: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 3. Try with only boolean filters
|
|
131
|
+
if (filterTerms.hasAttachments === true || filterTerms.unreadOnly === true) {
|
|
132
|
+
try {
|
|
133
|
+
console.error("Attempting search with only boolean filters");
|
|
134
|
+
searchAttempts.push("boolean-filters-only");
|
|
135
|
+
|
|
136
|
+
const filterOnlyParams = {
|
|
137
|
+
$top: count,
|
|
138
|
+
$select: config.EMAIL_SELECT_FIELDS,
|
|
139
|
+
$orderby: 'receivedDateTime desc'
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Add the boolean filters
|
|
143
|
+
addBooleanFilters(filterOnlyParams, filterTerms);
|
|
144
|
+
|
|
145
|
+
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, filterOnlyParams);
|
|
146
|
+
console.error(`Boolean filter search found ${response.value?.length || 0} results`);
|
|
147
|
+
return response;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error(`Boolean filter search failed: ${error.message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 4. Final fallback: just get recent emails
|
|
154
|
+
console.error("All search strategies failed, falling back to recent emails");
|
|
155
|
+
searchAttempts.push("recent-emails");
|
|
156
|
+
|
|
157
|
+
const basicParams = {
|
|
158
|
+
$top: count,
|
|
159
|
+
$select: config.EMAIL_SELECT_FIELDS,
|
|
160
|
+
$orderby: 'receivedDateTime desc'
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, basicParams);
|
|
164
|
+
console.error(`Fallback to recent emails found ${response.value?.length || 0} results`);
|
|
165
|
+
|
|
166
|
+
// Add a note to the response about the search attempts
|
|
167
|
+
response._searchInfo = {
|
|
168
|
+
attemptsCount: searchAttempts.length,
|
|
169
|
+
strategies: searchAttempts,
|
|
170
|
+
originalTerms: searchTerms,
|
|
171
|
+
filterTerms: filterTerms
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return response;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Build search parameters from search terms and filter terms
|
|
179
|
+
* @param {object} searchTerms - Search terms (query, from, to, subject)
|
|
180
|
+
* @param {object} filterTerms - Filter terms (hasAttachments, unreadOnly)
|
|
181
|
+
* @param {number} count - Maximum number of results
|
|
182
|
+
* @returns {object} - Query parameters
|
|
183
|
+
*/
|
|
184
|
+
function buildSearchParams(searchTerms, filterTerms, count) {
|
|
185
|
+
const params = {
|
|
186
|
+
$top: count,
|
|
187
|
+
$select: config.EMAIL_SELECT_FIELDS,
|
|
188
|
+
$orderby: 'receivedDateTime desc'
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Handle search terms
|
|
192
|
+
const kqlTerms = [];
|
|
193
|
+
|
|
194
|
+
if (searchTerms.query) {
|
|
195
|
+
// General query doesn't need a prefix
|
|
196
|
+
kqlTerms.push(searchTerms.query);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (searchTerms.subject) {
|
|
200
|
+
kqlTerms.push(`subject:"${searchTerms.subject}"`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (searchTerms.from) {
|
|
204
|
+
kqlTerms.push(`from:"${searchTerms.from}"`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (searchTerms.to) {
|
|
208
|
+
kqlTerms.push(`to:"${searchTerms.to}"`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Add $search if we have any search terms
|
|
212
|
+
if (kqlTerms.length > 0) {
|
|
213
|
+
params.$search = kqlTerms.join(' ');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Add boolean filters
|
|
217
|
+
addBooleanFilters(params, filterTerms);
|
|
218
|
+
|
|
219
|
+
return params;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Add boolean filters to query parameters
|
|
224
|
+
* @param {object} params - Query parameters
|
|
225
|
+
* @param {object} filterTerms - Filter terms (hasAttachments, unreadOnly)
|
|
226
|
+
*/
|
|
227
|
+
function addBooleanFilters(params, filterTerms) {
|
|
228
|
+
const filterConditions = [];
|
|
229
|
+
|
|
230
|
+
if (filterTerms.hasAttachments === true) {
|
|
231
|
+
filterConditions.push('hasAttachments eq true');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (filterTerms.unreadOnly === true) {
|
|
235
|
+
filterConditions.push('isRead eq false');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Add $filter parameter if we have any filter conditions
|
|
239
|
+
if (filterConditions.length > 0) {
|
|
240
|
+
params.$filter = filterConditions.join(' and ');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Format search results into a readable text format
|
|
246
|
+
* @param {object} response - The API response object
|
|
247
|
+
* @returns {object} - MCP response object
|
|
248
|
+
*/
|
|
249
|
+
function formatSearchResults(response) {
|
|
250
|
+
if (!response.value || response.value.length === 0) {
|
|
251
|
+
return {
|
|
252
|
+
content: [{
|
|
253
|
+
type: "text",
|
|
254
|
+
text: `No emails found matching your search criteria.`
|
|
255
|
+
}]
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Format results
|
|
260
|
+
const emailList = response.value.map((email, index) => {
|
|
261
|
+
const sender = email.from?.emailAddress || { name: 'Unknown', address: 'unknown' };
|
|
262
|
+
const date = new Date(email.receivedDateTime).toLocaleString();
|
|
263
|
+
const readStatus = email.isRead ? '' : '[UNREAD] ';
|
|
264
|
+
|
|
265
|
+
return `${index + 1}. ${readStatus}${date} - From: ${sender.name} (${sender.address})\nSubject: ${email.subject}\nID: ${email.id}\n`;
|
|
266
|
+
}).join("\n");
|
|
267
|
+
|
|
268
|
+
// Add search strategy info if available
|
|
269
|
+
let additionalInfo = '';
|
|
270
|
+
if (response._searchInfo) {
|
|
271
|
+
additionalInfo = `\n(Search used ${response._searchInfo.strategies[response._searchInfo.strategies.length - 1]} strategy)`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
content: [{
|
|
276
|
+
type: "text",
|
|
277
|
+
text: `Found ${response.value.length} emails matching your search criteria:${additionalInfo}\n\n${emailList}`
|
|
278
|
+
}]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = handleSearchEmails;
|
package/email/send.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send email functionality
|
|
3
|
+
*/
|
|
4
|
+
const config = require('../config');
|
|
5
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
6
|
+
const { ensureAuthenticated } = require('../auth');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Send email handler
|
|
10
|
+
* @param {object} args - Tool arguments
|
|
11
|
+
* @returns {object} - MCP response
|
|
12
|
+
*/
|
|
13
|
+
async function handleSendEmail(args) {
|
|
14
|
+
const { to, cc, bcc, subject, body, importance = 'normal', saveToSentItems = true } = args;
|
|
15
|
+
|
|
16
|
+
// Validate required parameters
|
|
17
|
+
if (!to) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{
|
|
20
|
+
type: "text",
|
|
21
|
+
text: "Recipient (to) is required."
|
|
22
|
+
}]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!subject) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: "Subject is required."
|
|
31
|
+
}]
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!body) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: "Body content is required."
|
|
40
|
+
}]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Get access token
|
|
46
|
+
const accessToken = await ensureAuthenticated();
|
|
47
|
+
|
|
48
|
+
// Format recipients
|
|
49
|
+
const toRecipients = to.split(',').map(email => {
|
|
50
|
+
email = email.trim();
|
|
51
|
+
return {
|
|
52
|
+
emailAddress: {
|
|
53
|
+
address: email
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const ccRecipients = cc ? cc.split(',').map(email => {
|
|
59
|
+
email = email.trim();
|
|
60
|
+
return {
|
|
61
|
+
emailAddress: {
|
|
62
|
+
address: email
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}) : [];
|
|
66
|
+
|
|
67
|
+
const bccRecipients = bcc ? bcc.split(',').map(email => {
|
|
68
|
+
email = email.trim();
|
|
69
|
+
return {
|
|
70
|
+
emailAddress: {
|
|
71
|
+
address: email
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}) : [];
|
|
75
|
+
|
|
76
|
+
// Prepare email object
|
|
77
|
+
const emailObject = {
|
|
78
|
+
message: {
|
|
79
|
+
subject,
|
|
80
|
+
body: {
|
|
81
|
+
contentType: body.includes('<html') ? 'html' : 'text',
|
|
82
|
+
content: body
|
|
83
|
+
},
|
|
84
|
+
toRecipients,
|
|
85
|
+
ccRecipients: ccRecipients.length > 0 ? ccRecipients : undefined,
|
|
86
|
+
bccRecipients: bccRecipients.length > 0 ? bccRecipients : undefined,
|
|
87
|
+
importance
|
|
88
|
+
},
|
|
89
|
+
saveToSentItems
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Make API call to send email
|
|
93
|
+
await callGraphAPI(accessToken, 'POST', 'me/sendMail', emailObject);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
content: [{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: `Email sent successfully!\n\nSubject: ${subject}\nRecipients: ${toRecipients.length}${ccRecipients.length > 0 ? ` + ${ccRecipients.length} CC` : ''}${bccRecipients.length > 0 ? ` + ${bccRecipients.length} BCC` : ''}\nMessage Length: ${body.length} characters`
|
|
99
|
+
}]
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (error.message === 'Authentication required') {
|
|
103
|
+
return {
|
|
104
|
+
content: [{
|
|
105
|
+
type: "text",
|
|
106
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
107
|
+
}]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
content: [{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: `Error sending email: ${error.message}`
|
|
115
|
+
}]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = handleSendEmail;
|
package/folder/create.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create folder functionality
|
|
3
|
+
*/
|
|
4
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
|
+
const { ensureAuthenticated } = require('../auth');
|
|
6
|
+
const { getFolderIdByName } = require('../email/folder-utils');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create folder handler
|
|
10
|
+
* @param {object} args - Tool arguments
|
|
11
|
+
* @returns {object} - MCP response
|
|
12
|
+
*/
|
|
13
|
+
async function handleCreateFolder(args) {
|
|
14
|
+
const folderName = args.name;
|
|
15
|
+
const parentFolder = args.parentFolder || '';
|
|
16
|
+
|
|
17
|
+
if (!folderName) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{
|
|
20
|
+
type: "text",
|
|
21
|
+
text: "Folder name is required."
|
|
22
|
+
}]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Get access token
|
|
28
|
+
const accessToken = await ensureAuthenticated();
|
|
29
|
+
|
|
30
|
+
// Create folder with appropriate parent
|
|
31
|
+
const result = await createMailFolder(accessToken, folderName, parentFolder);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: result.message
|
|
37
|
+
}]
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error.message === 'Authentication required') {
|
|
41
|
+
return {
|
|
42
|
+
content: [{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
45
|
+
}]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
content: [{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: `Error creating folder: ${error.message}`
|
|
53
|
+
}]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a new mail folder
|
|
60
|
+
* @param {string} accessToken - Access token
|
|
61
|
+
* @param {string} folderName - Name of the folder to create
|
|
62
|
+
* @param {string} parentFolderName - Name of the parent folder (optional)
|
|
63
|
+
* @returns {Promise<object>} - Result object with status and message
|
|
64
|
+
*/
|
|
65
|
+
async function createMailFolder(accessToken, folderName, parentFolderName) {
|
|
66
|
+
try {
|
|
67
|
+
// Check if a folder with this name already exists
|
|
68
|
+
const existingFolder = await getFolderIdByName(accessToken, folderName);
|
|
69
|
+
if (existingFolder) {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
message: `A folder named "${folderName}" already exists.`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// If parent folder specified, find its ID
|
|
77
|
+
let endpoint = 'me/mailFolders';
|
|
78
|
+
if (parentFolderName) {
|
|
79
|
+
const parentId = await getFolderIdByName(accessToken, parentFolderName);
|
|
80
|
+
if (!parentId) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
message: `Parent folder "${parentFolderName}" not found. Please specify a valid parent folder or leave it blank to create at the root level.`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
endpoint = `me/mailFolders/${parentId}/childFolders`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Create the folder
|
|
91
|
+
const folderData = {
|
|
92
|
+
displayName: folderName
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const response = await callGraphAPI(
|
|
96
|
+
accessToken,
|
|
97
|
+
'POST',
|
|
98
|
+
endpoint,
|
|
99
|
+
folderData
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (response && response.id) {
|
|
103
|
+
const locationInfo = parentFolderName
|
|
104
|
+
? `inside "${parentFolderName}"`
|
|
105
|
+
: "at the root level";
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
message: `Successfully created folder "${folderName}" ${locationInfo}.`,
|
|
110
|
+
folderId: response.id
|
|
111
|
+
};
|
|
112
|
+
} else {
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
message: "Failed to create folder. The server didn't return a folder ID."
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(`Error creating folder "${folderName}": ${error.message}`);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = handleCreateFolder;
|
package/folder/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Folder management module for Outlook MCP server
|
|
3
|
+
*/
|
|
4
|
+
const handleListFolders = require('./list');
|
|
5
|
+
const handleCreateFolder = require('./create');
|
|
6
|
+
const handleMoveEmails = require('./move');
|
|
7
|
+
|
|
8
|
+
// Folder management tool definitions
|
|
9
|
+
const folderTools = [
|
|
10
|
+
{
|
|
11
|
+
name: "list-folders",
|
|
12
|
+
description: "Lists mail folders in your Outlook account",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
includeItemCounts: {
|
|
17
|
+
type: "boolean",
|
|
18
|
+
description: "Include counts of total and unread items"
|
|
19
|
+
},
|
|
20
|
+
includeChildren: {
|
|
21
|
+
type: "boolean",
|
|
22
|
+
description: "Include child folders in hierarchy"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
required: []
|
|
26
|
+
},
|
|
27
|
+
handler: handleListFolders
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "create-folder",
|
|
31
|
+
description: "Creates a new mail folder",
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
name: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Name of the folder to create"
|
|
38
|
+
},
|
|
39
|
+
parentFolder: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Optional parent folder name (default is root)"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
required: ["name"]
|
|
45
|
+
},
|
|
46
|
+
handler: handleCreateFolder
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "move-emails",
|
|
50
|
+
description: "Moves emails from one folder to another",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
emailIds: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "Comma-separated list of email IDs to move"
|
|
57
|
+
},
|
|
58
|
+
targetFolder: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Name of the folder to move emails to"
|
|
61
|
+
},
|
|
62
|
+
sourceFolder: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "Optional name of the source folder (default is inbox)"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
required: ["emailIds", "targetFolder"]
|
|
68
|
+
},
|
|
69
|
+
handler: handleMoveEmails
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
folderTools,
|
|
75
|
+
handleListFolders,
|
|
76
|
+
handleCreateFolder,
|
|
77
|
+
handleMoveEmails
|
|
78
|
+
};
|