@tgai96/outlook-mcp 1.0.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/README.md +396 -0
- package/auth/index.js +64 -0
- package/auth/oauth-server.js +178 -0
- package/auth/token-manager.js +139 -0
- package/auth/token-storage.js +317 -0
- package/auth/tools.js +171 -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 +246 -0
- package/config.js +108 -0
- package/email/folder-utils.js +175 -0
- package/email/index.js +157 -0
- package/email/list.js +78 -0
- package/email/mark-as-read.js +101 -0
- package/email/read.js +128 -0
- package/email/search.js +285 -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 +148 -0
- package/package.json +54 -0
- package/rules/create.js +248 -0
- package/rules/index.js +175 -0
- package/rules/list.js +202 -0
- package/utils/graph-api.js +192 -0
- package/utils/mock-data.js +145 -0
- package/utils/odata-helpers.js +40 -0
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
|
+
};
|
package/folder/list.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List folders functionality
|
|
3
|
+
*/
|
|
4
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
|
+
const { ensureAuthenticated } = require('../auth');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* List folders handler
|
|
9
|
+
* @param {object} args - Tool arguments
|
|
10
|
+
* @returns {object} - MCP response
|
|
11
|
+
*/
|
|
12
|
+
async function handleListFolders(args) {
|
|
13
|
+
const includeItemCounts = args.includeItemCounts === true;
|
|
14
|
+
const includeChildren = args.includeChildren === true;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// Get access token
|
|
18
|
+
const accessToken = await ensureAuthenticated();
|
|
19
|
+
|
|
20
|
+
// Get all mail folders
|
|
21
|
+
const folders = await getAllFoldersHierarchy(accessToken, includeItemCounts);
|
|
22
|
+
|
|
23
|
+
// If including children, format as hierarchy
|
|
24
|
+
if (includeChildren) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: "text",
|
|
28
|
+
text: formatFolderHierarchy(folders, includeItemCounts)
|
|
29
|
+
}]
|
|
30
|
+
};
|
|
31
|
+
} else {
|
|
32
|
+
// Otherwise, format as flat list
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: formatFolderList(folders, includeItemCounts)
|
|
37
|
+
}]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.message === 'Authentication required') {
|
|
42
|
+
return {
|
|
43
|
+
content: [{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
46
|
+
}]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
content: [{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: `Error listing folders: ${error.message}`
|
|
54
|
+
}]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get all mail folders with hierarchy information
|
|
61
|
+
* @param {string} accessToken - Access token
|
|
62
|
+
* @param {boolean} includeItemCounts - Include item counts in response
|
|
63
|
+
* @returns {Promise<Array>} - Array of folder objects with hierarchy
|
|
64
|
+
*/
|
|
65
|
+
async function getAllFoldersHierarchy(accessToken, includeItemCounts) {
|
|
66
|
+
try {
|
|
67
|
+
// Determine select fields based on whether to include counts
|
|
68
|
+
const selectFields = includeItemCounts
|
|
69
|
+
? 'id,displayName,parentFolderId,childFolderCount,totalItemCount,unreadItemCount'
|
|
70
|
+
: 'id,displayName,parentFolderId,childFolderCount';
|
|
71
|
+
|
|
72
|
+
// Get all mail folders
|
|
73
|
+
const response = await callGraphAPI(
|
|
74
|
+
accessToken,
|
|
75
|
+
'GET',
|
|
76
|
+
'me/mailFolders',
|
|
77
|
+
null,
|
|
78
|
+
{
|
|
79
|
+
$top: 100,
|
|
80
|
+
$select: selectFields
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (!response.value) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Get child folders for folders with children
|
|
89
|
+
const foldersWithChildren = response.value.filter(f => f.childFolderCount > 0);
|
|
90
|
+
|
|
91
|
+
const childFolderPromises = foldersWithChildren.map(async (folder) => {
|
|
92
|
+
try {
|
|
93
|
+
const childResponse = await callGraphAPI(
|
|
94
|
+
accessToken,
|
|
95
|
+
'GET',
|
|
96
|
+
`me/mailFolders/${folder.id}/childFolders`,
|
|
97
|
+
null,
|
|
98
|
+
{ $select: selectFields }
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Add parent folder info to each child
|
|
102
|
+
const childFolders = childResponse.value || [];
|
|
103
|
+
childFolders.forEach(child => {
|
|
104
|
+
child.parentFolder = folder.displayName;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return childFolders;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error(`Error getting child folders for "${folder.displayName}": ${error.message}`);
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const childFolders = await Promise.all(childFolderPromises);
|
|
115
|
+
const allChildFolders = childFolders.flat();
|
|
116
|
+
|
|
117
|
+
// Add top-level flag to parent folders
|
|
118
|
+
const topLevelFolders = response.value.map(folder => ({
|
|
119
|
+
...folder,
|
|
120
|
+
isTopLevel: true
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
// Combine all folders
|
|
124
|
+
return [...topLevelFolders, ...allChildFolders];
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error(`Error getting all folders: ${error.message}`);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Format folders as a flat list
|
|
133
|
+
* @param {Array} folders - Array of folder objects
|
|
134
|
+
* @param {boolean} includeItemCounts - Whether to include item counts
|
|
135
|
+
* @returns {string} - Formatted list
|
|
136
|
+
*/
|
|
137
|
+
function formatFolderList(folders, includeItemCounts) {
|
|
138
|
+
if (!folders || folders.length === 0) {
|
|
139
|
+
return "No folders found.";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Sort folders alphabetically, with well-known folders first
|
|
143
|
+
const wellKnownFolderNames = ['Inbox', 'Drafts', 'Sent Items', 'Deleted Items', 'Junk Email', 'Archive'];
|
|
144
|
+
|
|
145
|
+
const sortedFolders = [...folders].sort((a, b) => {
|
|
146
|
+
// Well-known folders come first
|
|
147
|
+
const aIsWellKnown = wellKnownFolderNames.includes(a.displayName);
|
|
148
|
+
const bIsWellKnown = wellKnownFolderNames.includes(b.displayName);
|
|
149
|
+
|
|
150
|
+
if (aIsWellKnown && !bIsWellKnown) return -1;
|
|
151
|
+
if (!aIsWellKnown && bIsWellKnown) return 1;
|
|
152
|
+
|
|
153
|
+
if (aIsWellKnown && bIsWellKnown) {
|
|
154
|
+
// Sort well-known folders by their index in the array
|
|
155
|
+
return wellKnownFolderNames.indexOf(a.displayName) - wellKnownFolderNames.indexOf(b.displayName);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Sort other folders alphabetically
|
|
159
|
+
return a.displayName.localeCompare(b.displayName);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Format each folder
|
|
163
|
+
const folderLines = sortedFolders.map(folder => {
|
|
164
|
+
let folderInfo = folder.displayName;
|
|
165
|
+
|
|
166
|
+
// Add parent folder info if available
|
|
167
|
+
if (folder.parentFolder) {
|
|
168
|
+
folderInfo += ` (in ${folder.parentFolder})`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Add item counts if requested
|
|
172
|
+
if (includeItemCounts) {
|
|
173
|
+
const unreadCount = folder.unreadItemCount || 0;
|
|
174
|
+
const totalCount = folder.totalItemCount || 0;
|
|
175
|
+
folderInfo += ` - ${totalCount} items`;
|
|
176
|
+
|
|
177
|
+
if (unreadCount > 0) {
|
|
178
|
+
folderInfo += ` (${unreadCount} unread)`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return folderInfo;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return `Found ${folders.length} folders:\n\n${folderLines.join('\n')}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Format folders as a hierarchical tree
|
|
190
|
+
* @param {Array} folders - Array of folder objects
|
|
191
|
+
* @param {boolean} includeItemCounts - Whether to include item counts
|
|
192
|
+
* @returns {string} - Formatted hierarchy
|
|
193
|
+
*/
|
|
194
|
+
function formatFolderHierarchy(folders, includeItemCounts) {
|
|
195
|
+
if (!folders || folders.length === 0) {
|
|
196
|
+
return "No folders found.";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Build folder hierarchy
|
|
200
|
+
const folderMap = new Map();
|
|
201
|
+
const rootFolders = [];
|
|
202
|
+
|
|
203
|
+
// First pass: create map of all folders
|
|
204
|
+
folders.forEach(folder => {
|
|
205
|
+
folderMap.set(folder.id, {
|
|
206
|
+
...folder,
|
|
207
|
+
children: []
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (folder.isTopLevel) {
|
|
211
|
+
rootFolders.push(folder.id);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Second pass: build hierarchy
|
|
216
|
+
folders.forEach(folder => {
|
|
217
|
+
if (!folder.isTopLevel && folder.parentFolderId) {
|
|
218
|
+
const parent = folderMap.get(folder.parentFolderId);
|
|
219
|
+
if (parent) {
|
|
220
|
+
parent.children.push(folder.id);
|
|
221
|
+
} else {
|
|
222
|
+
// Fallback for orphaned folders
|
|
223
|
+
rootFolders.push(folder.id);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Format hierarchy recursively
|
|
229
|
+
function formatSubtree(folderId, level = 0) {
|
|
230
|
+
const folder = folderMap.get(folderId);
|
|
231
|
+
if (!folder) return '';
|
|
232
|
+
|
|
233
|
+
const indent = ' '.repeat(level);
|
|
234
|
+
let line = `${indent}${folder.displayName}`;
|
|
235
|
+
|
|
236
|
+
// Add item counts if requested
|
|
237
|
+
if (includeItemCounts) {
|
|
238
|
+
const unreadCount = folder.unreadItemCount || 0;
|
|
239
|
+
const totalCount = folder.totalItemCount || 0;
|
|
240
|
+
line += ` - ${totalCount} items`;
|
|
241
|
+
|
|
242
|
+
if (unreadCount > 0) {
|
|
243
|
+
line += ` (${unreadCount} unread)`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Add children
|
|
248
|
+
const childLines = folder.children
|
|
249
|
+
.map(childId => formatSubtree(childId, level + 1))
|
|
250
|
+
.filter(line => line.length > 0)
|
|
251
|
+
.join('\n');
|
|
252
|
+
|
|
253
|
+
return childLines.length > 0 ? `${line}\n${childLines}` : line;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Format all root folders
|
|
257
|
+
const formattedHierarchy = rootFolders
|
|
258
|
+
.map(folderId => formatSubtree(folderId))
|
|
259
|
+
.join('\n');
|
|
260
|
+
|
|
261
|
+
return `Folder Hierarchy:\n\n${formattedHierarchy}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = handleListFolders;
|
package/folder/move.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move emails functionality
|
|
3
|
+
*/
|
|
4
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
|
+
const { ensureAuthenticated } = require('../auth');
|
|
6
|
+
const { getFolderIdByName } = require('../email/folder-utils');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Move emails handler
|
|
10
|
+
* @param {object} args - Tool arguments
|
|
11
|
+
* @returns {object} - MCP response
|
|
12
|
+
*/
|
|
13
|
+
async function handleMoveEmails(args) {
|
|
14
|
+
const emailIds = args.emailIds || '';
|
|
15
|
+
const targetFolder = args.targetFolder || '';
|
|
16
|
+
const sourceFolder = args.sourceFolder || '';
|
|
17
|
+
|
|
18
|
+
if (!emailIds) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{
|
|
21
|
+
type: "text",
|
|
22
|
+
text: "Email IDs are required. Please provide a comma-separated list of email IDs to move."
|
|
23
|
+
}]
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!targetFolder) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: "Target folder name is required."
|
|
32
|
+
}]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// Get access token
|
|
38
|
+
const accessToken = await ensureAuthenticated();
|
|
39
|
+
|
|
40
|
+
// Parse email IDs
|
|
41
|
+
const ids = emailIds.split(',').map(id => id.trim()).filter(id => id);
|
|
42
|
+
|
|
43
|
+
if (ids.length === 0) {
|
|
44
|
+
return {
|
|
45
|
+
content: [{
|
|
46
|
+
type: "text",
|
|
47
|
+
text: "No valid email IDs provided."
|
|
48
|
+
}]
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Move emails
|
|
53
|
+
const result = await moveEmailsToFolder(accessToken, ids, targetFolder, sourceFolder);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
content: [{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: result.message
|
|
59
|
+
}]
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error.message === 'Authentication required') {
|
|
63
|
+
return {
|
|
64
|
+
content: [{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
67
|
+
}]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
content: [{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: `Error moving emails: ${error.message}`
|
|
75
|
+
}]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Move emails to a folder
|
|
82
|
+
* @param {string} accessToken - Access token
|
|
83
|
+
* @param {Array<string>} emailIds - Array of email IDs to move
|
|
84
|
+
* @param {string} targetFolderName - Name of the target folder
|
|
85
|
+
* @param {string} sourceFolderName - Name of the source folder (optional)
|
|
86
|
+
* @returns {Promise<object>} - Result object with status and message
|
|
87
|
+
*/
|
|
88
|
+
async function moveEmailsToFolder(accessToken, emailIds, targetFolderName, sourceFolderName) {
|
|
89
|
+
try {
|
|
90
|
+
// Get the target folder ID
|
|
91
|
+
const targetFolderId = await getFolderIdByName(accessToken, targetFolderName);
|
|
92
|
+
if (!targetFolderId) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
message: `Target folder "${targetFolderName}" not found. Please specify a valid folder name.`
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Track successful and failed moves
|
|
100
|
+
const results = {
|
|
101
|
+
successful: [],
|
|
102
|
+
failed: []
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Process each email one by one to handle errors independently
|
|
106
|
+
for (const emailId of emailIds) {
|
|
107
|
+
try {
|
|
108
|
+
// Move the email
|
|
109
|
+
await callGraphAPI(
|
|
110
|
+
accessToken,
|
|
111
|
+
'POST',
|
|
112
|
+
`me/messages/${emailId}/move`,
|
|
113
|
+
{
|
|
114
|
+
destinationId: targetFolderId
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
results.successful.push(emailId);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error(`Error moving email ${emailId}: ${error.message}`);
|
|
121
|
+
results.failed.push({
|
|
122
|
+
id: emailId,
|
|
123
|
+
error: error.message
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Generate result message
|
|
129
|
+
let message = '';
|
|
130
|
+
|
|
131
|
+
if (results.successful.length > 0) {
|
|
132
|
+
message += `Successfully moved ${results.successful.length} email(s) to "${targetFolderName}".`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (results.failed.length > 0) {
|
|
136
|
+
if (message) message += '\n\n';
|
|
137
|
+
message += `Failed to move ${results.failed.length} email(s). Errors:`;
|
|
138
|
+
|
|
139
|
+
// Show first few errors with details
|
|
140
|
+
const maxErrors = Math.min(results.failed.length, 3);
|
|
141
|
+
for (let i = 0; i < maxErrors; i++) {
|
|
142
|
+
const failure = results.failed[i];
|
|
143
|
+
message += `\n- Email ${i+1}: ${failure.error}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// If there are more errors, just mention the count
|
|
147
|
+
if (results.failed.length > maxErrors) {
|
|
148
|
+
message += `\n...and ${results.failed.length - maxErrors} more.`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
success: results.successful.length > 0,
|
|
154
|
+
message,
|
|
155
|
+
results
|
|
156
|
+
};
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`Error in moveEmailsToFolder: ${error.message}`);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = handleMoveEmails;
|
package/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Outlook MCP Server - Main entry point
|
|
4
|
+
*
|
|
5
|
+
* A Model Context Protocol server that provides access to
|
|
6
|
+
* Microsoft Outlook through the Microsoft Graph API.
|
|
7
|
+
*/
|
|
8
|
+
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
9
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
10
|
+
const config = require('./config');
|
|
11
|
+
|
|
12
|
+
// Import module tools
|
|
13
|
+
const { authTools } = require('./auth');
|
|
14
|
+
const { calendarTools } = require('./calendar');
|
|
15
|
+
const { emailTools } = require('./email');
|
|
16
|
+
const { folderTools } = require('./folder');
|
|
17
|
+
const { rulesTools } = require('./rules');
|
|
18
|
+
|
|
19
|
+
// Log startup information
|
|
20
|
+
console.error(`STARTING ${config.SERVER_NAME.toUpperCase()} MCP SERVER`);
|
|
21
|
+
console.error(`Test mode is ${config.USE_TEST_MODE ? 'enabled' : 'disabled'}`);
|
|
22
|
+
|
|
23
|
+
// Combine all tools
|
|
24
|
+
const TOOLS = [
|
|
25
|
+
...authTools,
|
|
26
|
+
...calendarTools,
|
|
27
|
+
...emailTools,
|
|
28
|
+
...folderTools,
|
|
29
|
+
...rulesTools
|
|
30
|
+
// Future modules: contactsTools, etc.
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Create server with tools capabilities
|
|
34
|
+
const server = new Server(
|
|
35
|
+
{ name: config.SERVER_NAME, version: config.SERVER_VERSION },
|
|
36
|
+
{
|
|
37
|
+
capabilities: {
|
|
38
|
+
tools: TOOLS.reduce((acc, tool) => {
|
|
39
|
+
acc[tool.name] = {};
|
|
40
|
+
return acc;
|
|
41
|
+
}, {})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Handle all requests
|
|
47
|
+
server.fallbackRequestHandler = async (request) => {
|
|
48
|
+
try {
|
|
49
|
+
const { method, params, id } = request;
|
|
50
|
+
console.error(`REQUEST: ${method} [${id}]`);
|
|
51
|
+
|
|
52
|
+
// Initialize handler
|
|
53
|
+
if (method === "initialize") {
|
|
54
|
+
console.error(`INITIALIZE REQUEST: ID [${id}]`);
|
|
55
|
+
return {
|
|
56
|
+
protocolVersion: "2024-11-05",
|
|
57
|
+
capabilities: {
|
|
58
|
+
tools: TOOLS.reduce((acc, tool) => {
|
|
59
|
+
acc[tool.name] = {};
|
|
60
|
+
return acc;
|
|
61
|
+
}, {})
|
|
62
|
+
},
|
|
63
|
+
serverInfo: { name: config.SERVER_NAME, version: config.SERVER_VERSION }
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Tools list handler
|
|
68
|
+
if (method === "tools/list") {
|
|
69
|
+
console.error(`TOOLS LIST REQUEST: ID [${id}]`);
|
|
70
|
+
console.error(`TOOLS COUNT: ${TOOLS.length}`);
|
|
71
|
+
console.error(`TOOLS NAMES: ${TOOLS.map(t => t.name).join(', ')}`);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
tools: TOOLS.map(tool => ({
|
|
75
|
+
name: tool.name,
|
|
76
|
+
description: tool.description,
|
|
77
|
+
inputSchema: tool.inputSchema
|
|
78
|
+
}))
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Required empty responses for other capabilities
|
|
83
|
+
if (method === "resources/list") return { resources: [] };
|
|
84
|
+
if (method === "prompts/list") return { prompts: [] };
|
|
85
|
+
|
|
86
|
+
// Tool call handler
|
|
87
|
+
if (method === "tools/call") {
|
|
88
|
+
try {
|
|
89
|
+
const { name, arguments: args = {} } = params || {};
|
|
90
|
+
|
|
91
|
+
console.error(`TOOL CALL: ${name}`);
|
|
92
|
+
|
|
93
|
+
// Find the tool handler
|
|
94
|
+
const tool = TOOLS.find(t => t.name === name);
|
|
95
|
+
|
|
96
|
+
if (tool && tool.handler) {
|
|
97
|
+
return await tool.handler(args);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Tool not found
|
|
101
|
+
return {
|
|
102
|
+
error: {
|
|
103
|
+
code: -32601,
|
|
104
|
+
message: `Tool not found: ${name}`
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error(`Error in tools/call:`, error);
|
|
109
|
+
return {
|
|
110
|
+
error: {
|
|
111
|
+
code: -32603,
|
|
112
|
+
message: `Error processing tool call: ${error.message}`
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// For any other method, return method not found
|
|
119
|
+
return {
|
|
120
|
+
error: {
|
|
121
|
+
code: -32601,
|
|
122
|
+
message: `Method not found: ${method}`
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error(`Error in fallbackRequestHandler:`, error);
|
|
127
|
+
return {
|
|
128
|
+
error: {
|
|
129
|
+
code: -32603,
|
|
130
|
+
message: `Error processing request: ${error.message}`
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Make the script executable
|
|
137
|
+
process.on('SIGTERM', () => {
|
|
138
|
+
console.error('SIGTERM received but staying alive');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Start the server
|
|
142
|
+
const transport = new StdioServerTransport();
|
|
143
|
+
server.connect(transport)
|
|
144
|
+
.then(() => console.error(`${config.SERVER_NAME} connected and listening`))
|
|
145
|
+
.catch(error => {
|
|
146
|
+
console.error(`Connection error: ${error.message}`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tgai96/outlook-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Claude to access Outlook data via Microsoft Graph API",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"outlook-mcp": "./cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"cli.js",
|
|
12
|
+
"config.js",
|
|
13
|
+
"auth/",
|
|
14
|
+
"calendar/",
|
|
15
|
+
"email/",
|
|
16
|
+
"folder/",
|
|
17
|
+
"rules/",
|
|
18
|
+
"utils/",
|
|
19
|
+
".env.example",
|
|
20
|
+
"claude-config-sample.json"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"start": "node index.js",
|
|
24
|
+
"inspect": "npx @modelcontextprotocol/inspector node index.js",
|
|
25
|
+
"test-all-tools": "node test-mcp-tools.js",
|
|
26
|
+
"test": "jest"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"claude",
|
|
30
|
+
"outlook",
|
|
31
|
+
"mcp",
|
|
32
|
+
"microsoft-graph",
|
|
33
|
+
"email",
|
|
34
|
+
"calendar",
|
|
35
|
+
"model-context-protocol"
|
|
36
|
+
],
|
|
37
|
+
"author": "tgai96",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.1.0",
|
|
44
|
+
"dotenv": "^16.5.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@modelcontextprotocol/inspector": "^0.10.2",
|
|
48
|
+
"jest": "^29.7.0",
|
|
49
|
+
"supertest": "^7.0.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=14.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|