@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
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cancel event functionality
|
|
3
|
+
*/
|
|
4
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
|
+
const { ensureAuthenticated } = require('../auth');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Cancel event handler
|
|
9
|
+
* @param {object} args - Tool arguments
|
|
10
|
+
* @returns {object} - MCP response
|
|
11
|
+
*/
|
|
12
|
+
async function handleCancelEvent(args) {
|
|
13
|
+
const { eventId, comment } = args;
|
|
14
|
+
|
|
15
|
+
if (!eventId) {
|
|
16
|
+
return {
|
|
17
|
+
content: [{
|
|
18
|
+
type: "text",
|
|
19
|
+
text: "Event ID is required to cancel an event."
|
|
20
|
+
}]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Get access token
|
|
26
|
+
const accessToken = await ensureAuthenticated();
|
|
27
|
+
|
|
28
|
+
// Build API endpoint
|
|
29
|
+
const endpoint = `me/events/${eventId}/cancel`;
|
|
30
|
+
|
|
31
|
+
// Request body
|
|
32
|
+
const body = {
|
|
33
|
+
comment: comment || "Cancelled via API"
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Make API call
|
|
37
|
+
await callGraphAPI(accessToken, 'POST', endpoint, body);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
content: [{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: `Event with ID ${eventId} has been successfully cancelled.`
|
|
43
|
+
}]
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.message === 'Authentication required') {
|
|
47
|
+
return {
|
|
48
|
+
content: [{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
51
|
+
}]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
content: [{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: `Error cancelling event: ${error.message}`
|
|
59
|
+
}]
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = handleCancelEvent;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create event functionality
|
|
3
|
+
*/
|
|
4
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
|
+
const { ensureAuthenticated } = require('../auth');
|
|
6
|
+
const { DEFAULT_TIMEZONE } = require('../config');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create event handler
|
|
10
|
+
* @param {object} args - Tool arguments
|
|
11
|
+
* @returns {object} - MCP response
|
|
12
|
+
*/
|
|
13
|
+
async function handleCreateEvent(args) {
|
|
14
|
+
const { subject, start, end, attendees, body } = args;
|
|
15
|
+
|
|
16
|
+
if (!subject || !start || !end) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: "Subject, start, and end times are required to create an event."
|
|
21
|
+
}]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Get access token
|
|
27
|
+
const accessToken = await ensureAuthenticated();
|
|
28
|
+
|
|
29
|
+
// Build API endpoint
|
|
30
|
+
const endpoint = `me/events`;
|
|
31
|
+
|
|
32
|
+
// Request body
|
|
33
|
+
const bodyContent = {
|
|
34
|
+
subject,
|
|
35
|
+
start: { dateTime: start.dateTime || start, timeZone: start.timeZone || DEFAULT_TIMEZONE },
|
|
36
|
+
end: { dateTime: end.dateTime || end, timeZone: end.timeZone || DEFAULT_TIMEZONE },
|
|
37
|
+
attendees: attendees?.map(email => ({ emailAddress: { address: email }, type: "required" })),
|
|
38
|
+
body: { contentType: "HTML", content: body || "" }
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Make API call
|
|
42
|
+
const response = await callGraphAPI(accessToken, 'POST', endpoint, bodyContent);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
content: [{
|
|
46
|
+
type: "text",
|
|
47
|
+
text: `Event '${subject}' has been successfully created.`
|
|
48
|
+
}]
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error.message === 'Authentication required') {
|
|
52
|
+
return {
|
|
53
|
+
content: [{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
56
|
+
}]
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
content: [{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: `Error creating event: ${error.message}`
|
|
64
|
+
}]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = handleCreateEvent;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decline event functionality
|
|
3
|
+
*/
|
|
4
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
|
+
const { ensureAuthenticated } = require('../auth');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Decline event handler
|
|
9
|
+
* @param {object} args - Tool arguments
|
|
10
|
+
* @returns {object} - MCP response
|
|
11
|
+
*/
|
|
12
|
+
async function handleDeclineEvent(args) {
|
|
13
|
+
const { eventId, comment } = args;
|
|
14
|
+
|
|
15
|
+
if (!eventId) {
|
|
16
|
+
return {
|
|
17
|
+
content: [{
|
|
18
|
+
type: "text",
|
|
19
|
+
text: "Event ID is required to decline an event."
|
|
20
|
+
}]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Get access token
|
|
26
|
+
const accessToken = await ensureAuthenticated();
|
|
27
|
+
|
|
28
|
+
// Build API endpoint
|
|
29
|
+
const endpoint = `me/events/${eventId}/decline`;
|
|
30
|
+
|
|
31
|
+
// Request body
|
|
32
|
+
const body = {
|
|
33
|
+
comment: comment || "Declined via API"
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Make API call
|
|
37
|
+
await callGraphAPI(accessToken, 'POST', endpoint, body);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
content: [{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: `Event with ID ${eventId} has been successfully declined.`
|
|
43
|
+
}]
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.message === 'Authentication required') {
|
|
47
|
+
return {
|
|
48
|
+
content: [{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
51
|
+
}]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
content: [{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: `Error declining event: ${error.message}`
|
|
59
|
+
}]
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = handleDeclineEvent;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delete event functionality
|
|
3
|
+
*/
|
|
4
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
|
+
const { ensureAuthenticated } = require('../auth');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Delete event handler
|
|
9
|
+
* @param {object} args - Tool arguments
|
|
10
|
+
* @returns {object} - MCP response
|
|
11
|
+
*/
|
|
12
|
+
async function handleDeleteEvent(args) {
|
|
13
|
+
const { eventId } = args;
|
|
14
|
+
|
|
15
|
+
if (!eventId) {
|
|
16
|
+
return {
|
|
17
|
+
content: [{
|
|
18
|
+
type: "text",
|
|
19
|
+
text: "Event ID is required to delete an event."
|
|
20
|
+
}]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Get access token
|
|
26
|
+
const accessToken = await ensureAuthenticated();
|
|
27
|
+
|
|
28
|
+
// Build API endpoint
|
|
29
|
+
const endpoint = `me/events/${eventId}`;
|
|
30
|
+
|
|
31
|
+
// Make API call
|
|
32
|
+
await callGraphAPI(accessToken, 'DELETE', endpoint);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
content: [{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: `Event with ID ${eventId} has been successfully deleted.`
|
|
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 deleting event: ${error.message}`
|
|
54
|
+
}]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = handleDeleteEvent;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar module for Outlook MCP server
|
|
3
|
+
*/
|
|
4
|
+
const handleListEvents = require('./list');
|
|
5
|
+
const handleDeclineEvent = require('./decline');
|
|
6
|
+
const handleCreateEvent = require('./create');
|
|
7
|
+
const handleCancelEvent = require('./cancel');
|
|
8
|
+
const handleDeleteEvent = require('./delete');
|
|
9
|
+
|
|
10
|
+
// Calendar tool definitions
|
|
11
|
+
const calendarTools = [
|
|
12
|
+
{
|
|
13
|
+
name: "list-events",
|
|
14
|
+
description: "Lists upcoming events from your calendar",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
count: {
|
|
19
|
+
type: "number",
|
|
20
|
+
description: "Number of events to retrieve (default: 10, max: 50)"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
required: []
|
|
24
|
+
},
|
|
25
|
+
handler: handleListEvents
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "decline-event",
|
|
29
|
+
description: "Declines a calendar event",
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
eventId: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "The ID of the event to decline"
|
|
36
|
+
},
|
|
37
|
+
comment: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Optional comment for declining the event"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
required: ["eventId"]
|
|
43
|
+
},
|
|
44
|
+
handler: handleDeclineEvent
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "create-event",
|
|
48
|
+
description: "Creates a new calendar event",
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
subject: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "The subject of the event"
|
|
55
|
+
},
|
|
56
|
+
start: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "The start time of the event in ISO 8601 format"
|
|
59
|
+
},
|
|
60
|
+
end: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "The end time of the event in ISO 8601 format"
|
|
63
|
+
},
|
|
64
|
+
attendees: {
|
|
65
|
+
type: "array",
|
|
66
|
+
items: {
|
|
67
|
+
type: "string"
|
|
68
|
+
},
|
|
69
|
+
description: "List of attendee email addresses"
|
|
70
|
+
},
|
|
71
|
+
body: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: "Optional body content for the event"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
required: ["subject", "start", "end"]
|
|
77
|
+
},
|
|
78
|
+
handler: handleCreateEvent
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "cancel-event",
|
|
82
|
+
description: "Cancels a calendar event",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
eventId: {
|
|
87
|
+
type: "string",
|
|
88
|
+
description: "The ID of the event to cancel"
|
|
89
|
+
},
|
|
90
|
+
comment: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Optional comment for cancelling the event"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
required: ["eventId"]
|
|
96
|
+
},
|
|
97
|
+
handler: handleCancelEvent
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "delete-event",
|
|
101
|
+
description: "Deletes a calendar event",
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: "object",
|
|
104
|
+
properties: {
|
|
105
|
+
eventId: {
|
|
106
|
+
type: "string",
|
|
107
|
+
description: "The ID of the event to delete"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
required: ["eventId"]
|
|
111
|
+
},
|
|
112
|
+
handler: handleDeleteEvent
|
|
113
|
+
}
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
calendarTools,
|
|
118
|
+
handleListEvents,
|
|
119
|
+
handleDeclineEvent,
|
|
120
|
+
handleCreateEvent,
|
|
121
|
+
handleCancelEvent,
|
|
122
|
+
handleDeleteEvent
|
|
123
|
+
};
|
package/calendar/list.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List events functionality
|
|
3
|
+
*/
|
|
4
|
+
const config = require('../config');
|
|
5
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
6
|
+
const { ensureAuthenticated } = require('../auth');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* List events handler
|
|
10
|
+
* @param {object} args - Tool arguments
|
|
11
|
+
* @returns {object} - MCP response
|
|
12
|
+
*/
|
|
13
|
+
async function handleListEvents(args) {
|
|
14
|
+
const count = Math.min(args.count || 10, config.MAX_RESULT_COUNT);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// Get access token
|
|
18
|
+
const accessToken = await ensureAuthenticated();
|
|
19
|
+
|
|
20
|
+
// Build API endpoint
|
|
21
|
+
let endpoint = 'me/events';
|
|
22
|
+
|
|
23
|
+
// Add query parameters
|
|
24
|
+
const queryParams = {
|
|
25
|
+
$top: count,
|
|
26
|
+
$orderby: 'start/dateTime',
|
|
27
|
+
$filter: `start/dateTime ge '${new Date().toISOString()}'`,
|
|
28
|
+
$select: config.CALENDAR_SELECT_FIELDS
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Make API call
|
|
32
|
+
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, queryParams);
|
|
33
|
+
|
|
34
|
+
if (!response.value || response.value.length === 0) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: "No calendar events found."
|
|
39
|
+
}]
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Format results
|
|
44
|
+
const eventList = response.value.map((event, index) => {
|
|
45
|
+
const startDate = new Date(event.start.dateTime).toLocaleString(event.start.timeZone);
|
|
46
|
+
const endDate = new Date(event.end.dateTime).toLocaleString(event.end.timeZone);
|
|
47
|
+
const location = event.location.displayName || 'No location';
|
|
48
|
+
|
|
49
|
+
return `${index + 1}. ${event.subject} - Location: ${location}\nStart: ${startDate}\nEnd: ${endDate}\nSubject: ${event.subject}\nSummary: ${event.bodyPreview}\nID: ${event.id}\n`;
|
|
50
|
+
}).join("\n");
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
content: [{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: `Found ${response.value.length} events:\n\n${eventList}`
|
|
56
|
+
}]
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error.message === 'Authentication required') {
|
|
60
|
+
return {
|
|
61
|
+
content: [{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
64
|
+
}]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
content: [{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: `Error listing events: ${error.message}`
|
|
72
|
+
}]
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = handleListEvents;
|
package/cli.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for outlook-mcp package
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx outlook-mcp - Start MCP server (default)
|
|
7
|
+
* npx outlook-mcp auth - Start authentication server
|
|
8
|
+
* npx outlook-mcp --help - Show help
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { spawn, exec } = require('child_process');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
|
|
15
|
+
// Load environment variables from .env file if it exists
|
|
16
|
+
require('dotenv').config();
|
|
17
|
+
|
|
18
|
+
// Load config utilities
|
|
19
|
+
const config = require('./config');
|
|
20
|
+
|
|
21
|
+
const command = process.argv[2];
|
|
22
|
+
|
|
23
|
+
// Show help if --help flag
|
|
24
|
+
if (command === '--help' || command === '-h') {
|
|
25
|
+
console.log(`
|
|
26
|
+
Outlook MCP Server
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
npx outlook-mcp Start the MCP server (default)
|
|
30
|
+
npx outlook-mcp auth Start the authentication server
|
|
31
|
+
npx outlook-mcp config Configure Azure credentials interactively
|
|
32
|
+
npx outlook-mcp test-all-tools Test all MCP tools with your stored tokens
|
|
33
|
+
npx outlook-mcp --help Show this help message
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
# Start MCP server
|
|
37
|
+
npx outlook-mcp
|
|
38
|
+
|
|
39
|
+
# Configure credentials (first time setup)
|
|
40
|
+
npx outlook-mcp config
|
|
41
|
+
|
|
42
|
+
# Authenticate with Microsoft
|
|
43
|
+
npx outlook-mcp auth
|
|
44
|
+
|
|
45
|
+
# Test all tools (requires authentication first)
|
|
46
|
+
npx outlook-mcp test-all-tools
|
|
47
|
+
|
|
48
|
+
This will start a local server on port 3333 and automatically open your browser
|
|
49
|
+
to authenticate with Microsoft. After successful authentication, tokens will be
|
|
50
|
+
saved to:
|
|
51
|
+
- macOS/Linux: ~/.outlook-mcp/tokens.json
|
|
52
|
+
- Windows: %USERPROFILE%\\.outlook-mcp\\tokens.json
|
|
53
|
+
|
|
54
|
+
Environment Variables:
|
|
55
|
+
MS_CLIENT_ID Your Microsoft Azure app client ID (required for auth - PKCE flow)
|
|
56
|
+
MS_CLIENT_SECRET Your Microsoft Azure app client secret (required for MCP server)
|
|
57
|
+
|
|
58
|
+
`);
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Function to open browser cross-platform
|
|
63
|
+
function openBrowser(url) {
|
|
64
|
+
const platform = os.platform();
|
|
65
|
+
let command;
|
|
66
|
+
|
|
67
|
+
switch (platform) {
|
|
68
|
+
case 'darwin': // macOS
|
|
69
|
+
command = `open "${url}"`;
|
|
70
|
+
break;
|
|
71
|
+
case 'win32': // Windows
|
|
72
|
+
command = `start "" "${url}"`;
|
|
73
|
+
break;
|
|
74
|
+
default: // Linux and others
|
|
75
|
+
command = `xdg-open "${url}"`;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
exec(command, (error) => {
|
|
80
|
+
if (error) {
|
|
81
|
+
// Browser opening failed, but that's okay - user can manually open
|
|
82
|
+
console.log(`\nā ļø Could not automatically open browser. Please visit:\n ${url}\n`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle config command
|
|
88
|
+
if (command === 'config') {
|
|
89
|
+
const readline = require('readline');
|
|
90
|
+
const rl = readline.createInterface({
|
|
91
|
+
input: process.stdin,
|
|
92
|
+
output: process.stdout
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
console.log('š Outlook MCP Configuration Setup\n');
|
|
96
|
+
const configHomeDir = process.env.HOME || process.env.USERPROFILE || os.homedir() || '/tmp';
|
|
97
|
+
const configPath = path.join(configHomeDir, '.outlook-mcp', 'config.json');
|
|
98
|
+
console.log(`This will save your Azure credentials to ${configPath}\n`);
|
|
99
|
+
console.log('Note: Authentication uses PKCE (no client_secret needed for auth),');
|
|
100
|
+
console.log(' but MS_CLIENT_SECRET is required for MCP server operations.\n');
|
|
101
|
+
|
|
102
|
+
rl.question('Enter MS_CLIENT_ID: ', (clientId) => {
|
|
103
|
+
if (!clientId || !clientId.trim()) {
|
|
104
|
+
console.error('\nā Client ID is required.');
|
|
105
|
+
rl.close();
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
rl.question('Enter MS_CLIENT_SECRET (required for MCP server): ', (clientSecret) => {
|
|
110
|
+
if (!clientSecret || !clientSecret.trim()) {
|
|
111
|
+
console.error('\nā Client Secret is required for MCP server operations.');
|
|
112
|
+
rl.close();
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
rl.question('Use test mode? (true/false, default: false): ', (useTestMode) => {
|
|
117
|
+
const configData = {
|
|
118
|
+
MS_CLIENT_ID: clientId.trim(),
|
|
119
|
+
MS_CLIENT_SECRET: clientSecret.trim(),
|
|
120
|
+
USE_TEST_MODE: useTestMode.trim().toLowerCase() === 'true' || false
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (config.saveConfig(configData)) {
|
|
124
|
+
console.log(`\nā
Configuration saved to ${configPath}`);
|
|
125
|
+
console.log(' You can now use npx outlook-mcp without environment variables!');
|
|
126
|
+
} else {
|
|
127
|
+
console.error('\nā Failed to save configuration.');
|
|
128
|
+
rl.close();
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
rl.close();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
} else if (command === 'auth') {
|
|
137
|
+
console.log('š Starting Outlook MCP Authentication Server...\n');
|
|
138
|
+
|
|
139
|
+
// Check if MS_CLIENT_ID is set (from env or config file)
|
|
140
|
+
const loadedConfig = config.loadConfig();
|
|
141
|
+
const clientId = process.env.MS_CLIENT_ID || loadedConfig.MS_CLIENT_ID;
|
|
142
|
+
|
|
143
|
+
if (!clientId) {
|
|
144
|
+
console.error('ā ļø Warning: MS_CLIENT_ID is not set.');
|
|
145
|
+
console.error(' Please set it using one of these methods:\n');
|
|
146
|
+
console.error(' 1. Run: npx outlook-mcp config');
|
|
147
|
+
console.error(' 2. Export: export MS_CLIENT_ID=your-client-id-here');
|
|
148
|
+
const configPath = path.join(process.env.HOME || process.env.USERPROFILE || os.homedir() || '/tmp', '.outlook-mcp', 'config.json');
|
|
149
|
+
console.error(` 3. Create ${configPath} with MS_CLIENT_ID`);
|
|
150
|
+
console.error(' 4. Create .env file with MS_CLIENT_ID=your-client-id-here\n');
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Start the auth server
|
|
155
|
+
const authServerPath = path.join(__dirname, 'outlook-auth-server.js');
|
|
156
|
+
const child = spawn('node', [authServerPath], {
|
|
157
|
+
stdio: 'inherit',
|
|
158
|
+
env: process.env
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Wait for server to start, then open browser
|
|
162
|
+
const authUrl = 'http://localhost:3333/auth';
|
|
163
|
+
|
|
164
|
+
// Function to check if server is ready and open browser
|
|
165
|
+
function checkServerAndOpenBrowser() {
|
|
166
|
+
const http = require('http');
|
|
167
|
+
const checkRequest = http.get('http://localhost:3333/', (res) => {
|
|
168
|
+
// Server is ready, open browser
|
|
169
|
+
console.log(`\nš Opening browser to: ${authUrl}\n`);
|
|
170
|
+
openBrowser(authUrl);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
checkRequest.on('error', () => {
|
|
174
|
+
// Server not ready yet, try again in 500ms
|
|
175
|
+
setTimeout(checkServerAndOpenBrowser, 500);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Start checking after a short delay
|
|
180
|
+
setTimeout(checkServerAndOpenBrowser, 1000);
|
|
181
|
+
|
|
182
|
+
// Handle process termination
|
|
183
|
+
process.on('SIGINT', () => {
|
|
184
|
+
console.log('\n\nShutting down authentication server...');
|
|
185
|
+
child.kill('SIGINT');
|
|
186
|
+
process.exit(0);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
process.on('SIGTERM', () => {
|
|
190
|
+
child.kill('SIGTERM');
|
|
191
|
+
process.exit(0);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
child.on('exit', (code) => {
|
|
195
|
+
process.exit(code || 0);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
child.on('error', (error) => {
|
|
199
|
+
console.error('Error starting authentication server:', error.message);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|
|
202
|
+
} else if (command === 'test-all-tools') {
|
|
203
|
+
// Run the test suite
|
|
204
|
+
const testScriptPath = path.join(__dirname, 'test-mcp-tools.js');
|
|
205
|
+
const child = spawn('node', [testScriptPath], {
|
|
206
|
+
stdio: 'inherit',
|
|
207
|
+
env: process.env
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
child.on('exit', (code) => {
|
|
211
|
+
process.exit(code || 0);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
child.on('error', (error) => {
|
|
215
|
+
console.error('Error running test suite:', error.message);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
// Default: Start the MCP server
|
|
220
|
+
// This is what gets run when npx outlook-mcp is called without arguments
|
|
221
|
+
const mcpServerPath = path.join(__dirname, 'index.js');
|
|
222
|
+
const child = spawn('node', [mcpServerPath], {
|
|
223
|
+
stdio: 'inherit',
|
|
224
|
+
env: process.env
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Handle process termination
|
|
228
|
+
process.on('SIGINT', () => {
|
|
229
|
+
child.kill('SIGINT');
|
|
230
|
+
process.exit(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
process.on('SIGTERM', () => {
|
|
234
|
+
child.kill('SIGTERM');
|
|
235
|
+
process.exit(0);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
child.on('exit', (code) => {
|
|
239
|
+
process.exit(code || 0);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
child.on('error', (error) => {
|
|
243
|
+
console.error('Error starting MCP server:', error.message);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
});
|
|
246
|
+
}
|