@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,139 @@
1
+ /**
2
+ * Token management for Microsoft Graph API authentication
3
+ */
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const config = require('../config');
7
+
8
+ // Global variable to store tokens
9
+ let cachedTokens = null;
10
+
11
+ /**
12
+ * Loads authentication tokens from the token file
13
+ * @returns {object|null} - The loaded tokens or null if not available
14
+ */
15
+ function loadTokenCache() {
16
+ try {
17
+ const tokenPath = config.AUTH_CONFIG.tokenStorePath;
18
+ console.error(`[DEBUG] Attempting to load tokens from: ${tokenPath}`);
19
+ console.error(`[DEBUG] HOME directory: ${process.env.HOME}`);
20
+ console.error(`[DEBUG] Full resolved path: ${tokenPath}`);
21
+
22
+ // Log file existence and details
23
+ if (!fs.existsSync(tokenPath)) {
24
+ console.error('[DEBUG] Token file does not exist');
25
+ return null;
26
+ }
27
+
28
+ const stats = fs.statSync(tokenPath);
29
+ console.error(`[DEBUG] Token file stats:
30
+ Size: ${stats.size} bytes
31
+ Created: ${stats.birthtime}
32
+ Modified: ${stats.mtime}`);
33
+
34
+ const tokenData = fs.readFileSync(tokenPath, 'utf8');
35
+ console.error('[DEBUG] Token file contents length:', tokenData.length);
36
+ console.error('[DEBUG] Token file first 200 characters:', tokenData.slice(0, 200));
37
+
38
+ try {
39
+ const tokens = JSON.parse(tokenData);
40
+ console.error('[DEBUG] Parsed tokens keys:', Object.keys(tokens));
41
+
42
+ // Log each key's value to see what's present
43
+ Object.keys(tokens).forEach(key => {
44
+ console.error(`[DEBUG] ${key}: ${typeof tokens[key]}`);
45
+ });
46
+
47
+ // Check for access token presence
48
+ if (!tokens.access_token) {
49
+ console.error('[DEBUG] No access_token found in tokens');
50
+ return null;
51
+ }
52
+
53
+ // Check token expiration
54
+ const now = Date.now();
55
+ const expiresAt = tokens.expires_at || 0;
56
+
57
+ console.error(`[DEBUG] Current time: ${now}`);
58
+ console.error(`[DEBUG] Token expires at: ${expiresAt}`);
59
+
60
+ if (now > expiresAt) {
61
+ console.error('[DEBUG] Token has expired');
62
+ return null;
63
+ }
64
+
65
+ // Update the cache
66
+ cachedTokens = tokens;
67
+ return tokens;
68
+ } catch (parseError) {
69
+ console.error('[DEBUG] Error parsing token JSON:', parseError);
70
+ return null;
71
+ }
72
+ } catch (error) {
73
+ console.error('[DEBUG] Error loading token cache:', error);
74
+ return null;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Saves authentication tokens to the token file
80
+ * @param {object} tokens - The tokens to save
81
+ * @returns {boolean} - Whether the save was successful
82
+ */
83
+ function saveTokenCache(tokens) {
84
+ try {
85
+ const tokenPath = config.AUTH_CONFIG.tokenStorePath;
86
+ console.error(`Saving tokens to: ${tokenPath}`);
87
+
88
+ // Ensure the directory exists
89
+ const tokenDir = path.dirname(tokenPath);
90
+ if (!fs.existsSync(tokenDir)) {
91
+ fs.mkdirSync(tokenDir, { recursive: true });
92
+ }
93
+
94
+ fs.writeFileSync(tokenPath, JSON.stringify(tokens, null, 2));
95
+ console.error('Tokens saved successfully');
96
+
97
+ // Update the cache
98
+ cachedTokens = tokens;
99
+ return true;
100
+ } catch (error) {
101
+ console.error('Error saving token cache:', error);
102
+ return false;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Gets the current access token, loading from cache if necessary
108
+ * @returns {string|null} - The access token or null if not available
109
+ */
110
+ function getAccessToken() {
111
+ if (cachedTokens && cachedTokens.access_token) {
112
+ return cachedTokens.access_token;
113
+ }
114
+
115
+ const tokens = loadTokenCache();
116
+ return tokens ? tokens.access_token : null;
117
+ }
118
+
119
+ /**
120
+ * Creates a test access token for use in test mode
121
+ * @returns {object} - The test tokens
122
+ */
123
+ function createTestTokens() {
124
+ const testTokens = {
125
+ access_token: "test_access_token_" + Date.now(),
126
+ refresh_token: "test_refresh_token_" + Date.now(),
127
+ expires_at: Date.now() + (3600 * 1000) // 1 hour
128
+ };
129
+
130
+ saveTokenCache(testTokens);
131
+ return testTokens;
132
+ }
133
+
134
+ module.exports = {
135
+ loadTokenCache,
136
+ saveTokenCache,
137
+ getAccessToken,
138
+ createTestTokens
139
+ };
@@ -0,0 +1,317 @@
1
+ const fs = require('fs').promises;
2
+ const path = require('path');
3
+ const https = require('https');
4
+ const querystring = require('querystring');
5
+
6
+ class TokenStorage {
7
+ constructor(config) {
8
+ this.config = {
9
+ tokenStorePath: path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.outlook-mcp', 'tokens.json'),
10
+ clientId: process.env.MS_CLIENT_ID,
11
+ redirectUri: process.env.MS_REDIRECT_URI || 'http://localhost:3333/auth/callback',
12
+ scopes: (process.env.MS_SCOPES || 'offline_access User.Read Mail.Read').split(' '),
13
+ tokenEndpoint: process.env.MS_TOKEN_ENDPOINT || 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
14
+ refreshTokenBuffer: 5 * 60 * 1000, // 5 minutes buffer for token refresh
15
+ ...config // Allow overriding default config
16
+ };
17
+ this.tokens = null;
18
+ this._loadPromise = null;
19
+ this._refreshPromise = null;
20
+
21
+ if (!this.config.clientId) {
22
+ console.warn("TokenStorage: MS_CLIENT_ID is not configured. Token operations might fail.");
23
+ }
24
+ }
25
+
26
+ async _loadTokensFromFile() {
27
+ try {
28
+ const tokenData = await fs.readFile(this.config.tokenStorePath, 'utf8');
29
+ this.tokens = JSON.parse(tokenData);
30
+ console.error('Tokens loaded from file.');
31
+ return this.tokens;
32
+ } catch (error) {
33
+ if (error.code === 'ENOENT') {
34
+ console.error('Token file not found. No tokens loaded.');
35
+ } else {
36
+ console.error('Error loading token cache:', error);
37
+ }
38
+ this.tokens = null;
39
+ return null;
40
+ }
41
+ }
42
+
43
+ async _saveTokensToFile() {
44
+ if (!this.tokens) {
45
+ console.warn('No tokens to save.');
46
+ return false;
47
+ }
48
+ try {
49
+ // Ensure the directory exists
50
+ const tokenDir = path.dirname(this.config.tokenStorePath);
51
+ await fs.mkdir(tokenDir, { recursive: true });
52
+
53
+ await fs.writeFile(this.config.tokenStorePath, JSON.stringify(this.tokens, null, 2));
54
+ console.error('Tokens saved successfully.');
55
+ // return true; // No longer returning boolean, will throw on error.
56
+ } catch (error) {
57
+ console.error('Error saving token cache:', error);
58
+ throw error; // Propagate the error
59
+ }
60
+ }
61
+
62
+ async getTokens() {
63
+ if (this.tokens) {
64
+ return this.tokens;
65
+ }
66
+ if (!this._loadPromise) {
67
+ this._loadPromise = this._loadTokensFromFile().finally(() => {
68
+ this._loadPromise = null; // Reset promise once completed
69
+ });
70
+ }
71
+ return this._loadPromise;
72
+ }
73
+
74
+ getExpiryTime() {
75
+ return this.tokens && this.tokens.expires_at ? this.tokens.expires_at : 0;
76
+ }
77
+
78
+ isTokenExpired() {
79
+ if (!this.tokens || !this.tokens.expires_at) {
80
+ return true; // No token or no expiry means it's effectively expired or invalid
81
+ }
82
+ // Check if current time is past expiry time, considering a buffer
83
+ return Date.now() >= (this.tokens.expires_at - this.config.refreshTokenBuffer);
84
+ }
85
+
86
+ async getValidAccessToken() {
87
+ await this.getTokens(); // Ensure tokens are loaded
88
+
89
+ if (!this.tokens || !this.tokens.access_token) {
90
+ console.error('[TokenStorage] No access token available.');
91
+ return null;
92
+ }
93
+
94
+ if (this.isTokenExpired()) {
95
+ console.error('[TokenStorage] Access token expired or nearing expiration. Attempting refresh.');
96
+ console.error(`[TokenStorage] Client ID configured: ${this.config.clientId ? 'YES' : 'NO'}`);
97
+ if (this.tokens.refresh_token) {
98
+ try {
99
+ return await this.refreshAccessToken();
100
+ } catch (refreshError) {
101
+ console.error('[TokenStorage] Failed to refresh access token:', refreshError.message);
102
+ this.tokens = null; // Invalidate tokens on refresh failure
103
+ await this._saveTokensToFile(); // Persist invalidation
104
+ return null;
105
+ }
106
+ } else {
107
+ console.error('[TokenStorage] No refresh token available. Cannot refresh access token.');
108
+ this.tokens = null; // Invalidate tokens as they are expired and cannot be refreshed
109
+ await this._saveTokensToFile(); // Persist invalidation
110
+ return null;
111
+ }
112
+ }
113
+ console.error('[TokenStorage] Access token is still valid');
114
+ return this.tokens.access_token;
115
+ }
116
+
117
+ async refreshAccessToken() {
118
+ if (!this.tokens || !this.tokens.refresh_token) {
119
+ throw new Error('No refresh token available to refresh the access token.');
120
+ }
121
+
122
+ if (!this.config.clientId) {
123
+ throw new Error('MS_CLIENT_ID is not configured. Cannot refresh access token.');
124
+ }
125
+
126
+ // Prevent multiple concurrent refresh attempts
127
+ if (this._refreshPromise) {
128
+ console.error("[TokenStorage] Refresh already in progress, returning existing promise.");
129
+ return this._refreshPromise.then(tokens => tokens.access_token);
130
+ }
131
+
132
+ console.error('[TokenStorage] Attempting to refresh access token...');
133
+ console.error(`[TokenStorage] Using client ID: ${this.config.clientId.substring(0, 8)}...`);
134
+
135
+ // Use scopes from existing token if available (preserves original permissions),
136
+ // otherwise use config scopes. This ensures we don't lose permissions on refresh.
137
+ let refreshScopes = this.config.scopes;
138
+ if (this.tokens.scope) {
139
+ // If token has scope as string, use it; if array, join it
140
+ refreshScopes = typeof this.tokens.scope === 'string'
141
+ ? this.tokens.scope.split(' ')
142
+ : this.tokens.scope;
143
+ console.error(`[TokenStorage] Using scopes from existing token: ${refreshScopes.join(' ')}`);
144
+ } else {
145
+ console.error(`[TokenStorage] Using scopes from config: ${refreshScopes.join(' ')}`);
146
+ }
147
+
148
+ // For public clients (PKCE), refresh tokens don't require client_secret
149
+ const postData = querystring.stringify({
150
+ client_id: this.config.clientId,
151
+ grant_type: 'refresh_token',
152
+ refresh_token: this.tokens.refresh_token,
153
+ scope: refreshScopes.join(' ')
154
+ });
155
+
156
+ const requestOptions = {
157
+ method: 'POST',
158
+ headers: {
159
+ 'Content-Type': 'application/x-www-form-urlencoded',
160
+ 'Content-Length': Buffer.byteLength(postData)
161
+ }
162
+ };
163
+
164
+ this._refreshPromise = new Promise((resolve, reject) => {
165
+ const req = https.request(this.config.tokenEndpoint, requestOptions, (res) => {
166
+ let data = '';
167
+ res.on('data', (chunk) => data += chunk);
168
+ res.on('end', async () => {
169
+ try {
170
+ const responseBody = JSON.parse(data);
171
+ const statusCode = res.statusCode || 500;
172
+ if (statusCode >= 200 && statusCode < 300) {
173
+ this.tokens.access_token = responseBody.access_token;
174
+ // Microsoft Graph API refresh tokens may or may not return a new refresh_token
175
+ if (responseBody.refresh_token) {
176
+ this.tokens.refresh_token = responseBody.refresh_token;
177
+ }
178
+ this.tokens.expires_in = responseBody.expires_in;
179
+ this.tokens.expires_at = Date.now() + (responseBody.expires_in * 1000);
180
+ try {
181
+ await this._saveTokensToFile();
182
+ console.error('Access token refreshed and saved successfully.');
183
+ resolve(this.tokens);
184
+ } catch (saveError) {
185
+ console.error('Failed to save refreshed tokens:', saveError);
186
+ // Even if save fails, tokens are updated in memory.
187
+ // Depending on desired strictness, could reject here.
188
+ // For now, resolve with in-memory tokens but log critical error.
189
+ // Or, to be stricter and align with re-throwing:
190
+ reject(new Error(`Access token refreshed but failed to save: ${saveError.message}`));
191
+ }
192
+ } else {
193
+ console.error('Error refreshing token:', responseBody);
194
+ reject(new Error(responseBody.error_description || `Token refresh failed with status ${statusCode}`));
195
+ }
196
+ } catch (e) { // Catch any error during parsing or saving
197
+ console.error('Error processing refresh token response or saving tokens:', e);
198
+ reject(e);
199
+ } finally {
200
+ this._refreshPromise = null; // Clear promise after completion
201
+ }
202
+ });
203
+ });
204
+ req.on('error', (error) => {
205
+ console.error('HTTP error during token refresh:', error);
206
+ reject(error);
207
+ this._refreshPromise = null; // Clear promise on error
208
+ });
209
+ req.write(postData);
210
+ req.end();
211
+ });
212
+
213
+ return this._refreshPromise.then(tokens => tokens.access_token);
214
+ }
215
+
216
+
217
+ async exchangeCodeForTokens(authCode, codeVerifier = null) {
218
+ if (!this.config.clientId) {
219
+ throw new Error("Client ID is not configured. Cannot exchange code for tokens.");
220
+ }
221
+ console.error('Exchanging authorization code for tokens...');
222
+
223
+ // Build token exchange request - use PKCE if codeVerifier is provided, otherwise assume client_secret (for backward compatibility)
224
+ const tokenData = {
225
+ client_id: this.config.clientId,
226
+ grant_type: 'authorization_code',
227
+ code: authCode,
228
+ redirect_uri: this.config.redirectUri
229
+ };
230
+
231
+ // For PKCE (public client), use code_verifier instead of client_secret
232
+ if (codeVerifier) {
233
+ tokenData.code_verifier = codeVerifier;
234
+ } else {
235
+ // Fallback for confidential clients (if client_secret is still configured)
236
+ const clientSecret = process.env.MS_CLIENT_SECRET;
237
+ if (clientSecret) {
238
+ tokenData.client_secret = clientSecret;
239
+ } else {
240
+ throw new Error("Either code_verifier (for PKCE) or client_secret must be provided for token exchange.");
241
+ }
242
+ }
243
+
244
+ const postData = querystring.stringify(tokenData);
245
+
246
+ const requestOptions = {
247
+ method: 'POST',
248
+ headers: {
249
+ 'Content-Type': 'application/x-www-form-urlencoded',
250
+ 'Content-Length': Buffer.byteLength(postData)
251
+ }
252
+ };
253
+
254
+ return new Promise((resolve, reject) => {
255
+ const req = https.request(this.config.tokenEndpoint, requestOptions, (res) => {
256
+ let data = '';
257
+ res.on('data', (chunk) => data += chunk);
258
+ res.on('end', async () => {
259
+ try {
260
+ const responseBody = JSON.parse(data);
261
+ const statusCode = res.statusCode || 500;
262
+ if (statusCode >= 200 && statusCode < 300) {
263
+ this.tokens = {
264
+ access_token: responseBody.access_token,
265
+ refresh_token: responseBody.refresh_token,
266
+ expires_in: responseBody.expires_in,
267
+ expires_at: Date.now() + (responseBody.expires_in * 1000),
268
+ scope: responseBody.scope,
269
+ token_type: responseBody.token_type
270
+ };
271
+ try {
272
+ await this._saveTokensToFile();
273
+ console.error('Tokens exchanged and saved successfully.');
274
+ resolve(this.tokens);
275
+ } catch (saveError) {
276
+ console.error('Failed to save exchanged tokens:', saveError);
277
+ // Similar to refresh, tokens are in memory but not persisted.
278
+ // Rejecting to indicate the operation wasn't fully successful.
279
+ reject(new Error(`Tokens exchanged but failed to save: ${saveError.message}`));
280
+ }
281
+ } else {
282
+ console.error('Error exchanging code for tokens:', responseBody);
283
+ reject(new Error(responseBody.error_description || `Token exchange failed with status ${statusCode}`));
284
+ }
285
+ } catch (e) { // Catch any error during parsing or saving
286
+ console.error('Error processing token exchange response or saving tokens:', e, "Raw data:", data);
287
+ reject(new Error(`Error processing token response: ${e.message}. Response data: ${data}`));
288
+ }
289
+ });
290
+ });
291
+ req.on('error', (error) => {
292
+ console.error('HTTP error during code exchange:', error);
293
+ reject(error);
294
+ });
295
+ req.write(postData);
296
+ req.end();
297
+ });
298
+ }
299
+
300
+ // Utility to clear tokens, e.g., for logout or forcing re-auth
301
+ async clearTokens() {
302
+ this.tokens = null;
303
+ try {
304
+ await fs.unlink(this.config.tokenStorePath);
305
+ console.error('Token file deleted successfully.');
306
+ } catch (error) {
307
+ if (error.code === 'ENOENT') {
308
+ console.error('Token file not found, nothing to delete.');
309
+ } else {
310
+ console.error('Error deleting token file:', error);
311
+ }
312
+ }
313
+ }
314
+ }
315
+
316
+ module.exports = TokenStorage;
317
+ // Adding a newline at the end of the file as requested by Gemini Code Assist
package/auth/tools.js ADDED
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Authentication-related tools for the Outlook MCP server
3
+ */
4
+ const config = require('../config');
5
+ const tokenManager = require('./token-manager');
6
+ const { ensureAuthenticated, tokenStorage } = require('./index');
7
+
8
+ /**
9
+ * About tool handler
10
+ * @returns {object} - MCP response
11
+ */
12
+ async function handleAbout() {
13
+ return {
14
+ content: [{
15
+ type: "text",
16
+ text: `📧 MODULAR Outlook Assistant MCP Server v${config.SERVER_VERSION} 📧\n\nProvides access to Microsoft Outlook email, calendar, and contacts through Microsoft Graph API.\nImplemented with a modular architecture for improved maintainability.`
17
+ }]
18
+ };
19
+ }
20
+
21
+ /**
22
+ * Authentication tool handler
23
+ * @param {object} args - Tool arguments
24
+ * @returns {object} - MCP response
25
+ */
26
+ async function handleAuthenticate(args) {
27
+ const force = args && args.force === true;
28
+
29
+ // For test mode, create a test token
30
+ if (config.USE_TEST_MODE) {
31
+ // Create a test token with a 1-hour expiry
32
+ tokenManager.createTestTokens();
33
+
34
+ return {
35
+ content: [{
36
+ type: "text",
37
+ text: 'Successfully authenticated with Microsoft Graph API (test mode)'
38
+ }]
39
+ };
40
+ }
41
+
42
+ // If not forcing, try to get a valid token first (will auto-refresh if needed)
43
+ if (!force) {
44
+ try {
45
+ const accessToken = await ensureAuthenticated();
46
+ if (accessToken) {
47
+ console.error('[AUTHENTICATE] Successfully obtained valid access token (may have been refreshed)');
48
+ return {
49
+ content: [{
50
+ type: "text",
51
+ text: 'Successfully authenticated with Microsoft Graph API. Access token is valid and ready to use.'
52
+ }]
53
+ };
54
+ }
55
+ } catch (error) {
56
+ console.error('[AUTHENTICATE] Failed to get valid token:', error.message);
57
+ // Continue to show auth URL if token refresh failed
58
+ }
59
+ }
60
+
61
+ // If force=true or token refresh failed, generate an auth URL and instruct the user to visit it
62
+ const authUrl = `${config.AUTH_CONFIG.authServerUrl}/auth?client_id=${config.AUTH_CONFIG.clientId}`;
63
+
64
+ return {
65
+ content: [{
66
+ type: "text",
67
+ text: `Authentication required. Please visit the following URL to authenticate with Microsoft: ${authUrl}\n\nAfter authentication, you will be redirected back to this application.`
68
+ }]
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Check authentication status tool handler
74
+ * @returns {object} - MCP response
75
+ */
76
+ async function handleCheckAuthStatus() {
77
+ console.error('[CHECK-AUTH-STATUS] Starting authentication status check');
78
+
79
+ const tokens = tokenManager.loadTokenCache();
80
+
81
+ console.error(`[CHECK-AUTH-STATUS] Tokens loaded: ${tokens ? 'YES' : 'NO'}`);
82
+
83
+ if (!tokens || !tokens.access_token) {
84
+ console.error('[CHECK-AUTH-STATUS] No valid access token found');
85
+ return {
86
+ content: [{ type: "text", text: "Not authenticated" }]
87
+ };
88
+ }
89
+
90
+ console.error('[CHECK-AUTH-STATUS] Access token present');
91
+
92
+ // Format timestamps in human-readable format
93
+ const now = Date.now();
94
+ const expiresAt = tokens.expires_at || 0;
95
+ const currentTime = new Date(now).toLocaleString();
96
+ const expiresTime = new Date(expiresAt).toLocaleString();
97
+
98
+ // Calculate time until expiration
99
+ const timeUntilExpiry = expiresAt - now;
100
+ const minutesUntilExpiry = Math.floor(timeUntilExpiry / (1000 * 60));
101
+ const hoursUntilExpiry = Math.floor(minutesUntilExpiry / 60);
102
+ const daysUntilExpiry = Math.floor(hoursUntilExpiry / 24);
103
+
104
+ let expiryMessage;
105
+ if (timeUntilExpiry < 0) {
106
+ expiryMessage = 'EXPIRED';
107
+ } else if (daysUntilExpiry > 0) {
108
+ expiryMessage = `${daysUntilExpiry} day(s) and ${hoursUntilExpiry % 24} hour(s) from now`;
109
+ } else if (hoursUntilExpiry > 0) {
110
+ expiryMessage = `${hoursUntilExpiry} hour(s) and ${minutesUntilExpiry % 60} minute(s) from now`;
111
+ } else {
112
+ expiryMessage = `${minutesUntilExpiry} minute(s) from now`;
113
+ }
114
+
115
+ console.error(`[CHECK-AUTH-STATUS] Current time: ${currentTime}`);
116
+ console.error(`[CHECK-AUTH-STATUS] Token expires at: ${expiresTime} (${expiryMessage})`);
117
+
118
+ const statusText = timeUntilExpiry < 0
119
+ ? `Authenticated but token has expired. Will auto-refresh on next use.`
120
+ : `Authenticated and ready. Token expires ${expiryMessage}.`;
121
+
122
+ return {
123
+ content: [{ type: "text", text: statusText }]
124
+ };
125
+ }
126
+
127
+ // Tool definitions
128
+ const authTools = [
129
+ {
130
+ name: "about",
131
+ description: "Returns information about this Outlook Assistant server",
132
+ inputSchema: {
133
+ type: "object",
134
+ properties: {},
135
+ required: []
136
+ },
137
+ handler: handleAbout
138
+ },
139
+ {
140
+ name: "authenticate",
141
+ description: "Authenticate with Microsoft Graph API to access Outlook data",
142
+ inputSchema: {
143
+ type: "object",
144
+ properties: {
145
+ force: {
146
+ type: "boolean",
147
+ description: "Force re-authentication even if already authenticated"
148
+ }
149
+ },
150
+ required: []
151
+ },
152
+ handler: handleAuthenticate
153
+ },
154
+ {
155
+ name: "check-auth-status",
156
+ description: "Check the current authentication status with Microsoft Graph API",
157
+ inputSchema: {
158
+ type: "object",
159
+ properties: {},
160
+ required: []
161
+ },
162
+ handler: handleCheckAuthStatus
163
+ }
164
+ ];
165
+
166
+ module.exports = {
167
+ authTools,
168
+ handleAbout,
169
+ handleAuthenticate,
170
+ handleCheckAuthStatus
171
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Accept event functionality
3
+ */
4
+ const { callGraphAPI } = require('../utils/graph-api');
5
+ const { ensureAuthenticated } = require('../auth');
6
+
7
+ /**
8
+ * Accept event handler
9
+ * @param {object} args - Tool arguments
10
+ * @returns {object} - MCP response
11
+ */
12
+ async function handleAcceptEvent(args) {
13
+ const { eventId, comment } = args;
14
+
15
+ if (!eventId) {
16
+ return {
17
+ content: [{
18
+ type: "text",
19
+ text: "Event ID is required to accept 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}/accept`;
30
+
31
+ // Request body
32
+ const body = {
33
+ comment: comment || "Accepted 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 accepted.`
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 accepting event: ${error.message}`
59
+ }]
60
+ };
61
+ }
62
+ }
63
+
64
+ module.exports = handleAcceptEvent;