@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.
@@ -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
+ };
@@ -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
+ }