@softeria/ms-365-mcp-server 0.9.0 → 0.9.2

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 CHANGED
@@ -132,11 +132,13 @@ When running as an MCP server, the following options can be used:
132
132
  --http [port] Use Streamable HTTP transport instead of stdio (optionally specify port, default: 3000)
133
133
  Starts Express.js server with MCP endpoint at /mcp
134
134
  --enable-auth-tools Enable login/logout tools when using HTTP mode (disabled by default in HTTP mode)
135
+ --enabled-tools <pattern> Filter tools using regex pattern (e.g., "excel|contact" to enable Excel and Contact tools)
135
136
  ```
136
137
 
137
138
  Environment variables:
138
139
 
139
140
  - `READ_ONLY=true|1`: Alternative to --read-only flag
141
+ - `ENABLED_TOOLS`: Filter tools using regex pattern (alternative to --enabled-tools flag)
140
142
  - `LOG_LEVEL`: Set logging level (default: 'info')
141
143
  - `SILENT=true`: Disable console output
142
144
  - `MS365_MCP_CLIENT_ID`: Custom Azure app client ID (defaults to built-in app)
package/dist/auth.js CHANGED
@@ -24,9 +24,12 @@ const SCOPE_HIERARCHY = {
24
24
  'Tasks.ReadWrite': ['Tasks.Read'],
25
25
  'Contacts.ReadWrite': ['Contacts.Read'],
26
26
  };
27
- function buildScopesFromEndpoints() {
27
+ function buildScopesFromEndpoints(includeWorkAccountScopes = false) {
28
28
  const scopesSet = new Set();
29
29
  endpoints.default.forEach((endpoint) => {
30
+ if (endpoint.requiresWorkAccount && !includeWorkAccountScopes) {
31
+ return;
32
+ }
30
33
  if (endpoint.scopes && Array.isArray(endpoint.scopes)) {
31
34
  endpoint.scopes.forEach((scope) => scopesSet.add(scope));
32
35
  }
@@ -39,6 +42,9 @@ function buildScopesFromEndpoints() {
39
42
  });
40
43
  return Array.from(scopesSet);
41
44
  }
45
+ function buildAllScopes() {
46
+ return buildScopesFromEndpoints(true);
47
+ }
42
48
  class AuthManager {
43
49
  constructor(config = DEFAULT_CONFIG, scopes = buildScopesFromEndpoints()) {
44
50
  logger.info(`And scopes are ${scopes.join(', ')}`, scopes);
@@ -226,5 +232,71 @@ class AuthManager {
226
232
  throw error;
227
233
  }
228
234
  }
235
+ async hasWorkAccountPermissions() {
236
+ try {
237
+ const accounts = await this.msalApp.getTokenCache().getAllAccounts();
238
+ if (accounts.length === 0) {
239
+ return false;
240
+ }
241
+ const workScopes = endpoints.default
242
+ .filter((e) => e.requiresWorkAccount)
243
+ .flatMap((e) => e.scopes || []);
244
+ try {
245
+ await this.msalApp.acquireTokenSilent({
246
+ scopes: workScopes.slice(0, 1),
247
+ account: accounts[0],
248
+ });
249
+ return true;
250
+ }
251
+ catch {
252
+ return false;
253
+ }
254
+ }
255
+ catch (error) {
256
+ logger.error(`Error checking work account permissions: ${error.message}`);
257
+ return false;
258
+ }
259
+ }
260
+ async expandToWorkAccountScopes(hack) {
261
+ try {
262
+ logger.info('Expanding to work account scopes...');
263
+ const allScopes = buildAllScopes();
264
+ const deviceCodeRequest = {
265
+ scopes: allScopes,
266
+ deviceCodeCallback: (response) => {
267
+ const text = [
268
+ '\n',
269
+ '🔄 This feature requires additional permissions (work account scopes)',
270
+ '\n',
271
+ response.message,
272
+ '\n',
273
+ ].join('');
274
+ if (hack) {
275
+ hack(text + 'After login run the "verify login" command');
276
+ }
277
+ else {
278
+ console.log(text);
279
+ }
280
+ logger.info('Work account scope expansion initiated');
281
+ },
282
+ };
283
+ const response = await this.msalApp.acquireTokenByDeviceCode(deviceCodeRequest);
284
+ logger.info('Work account scope expansion successful');
285
+ this.accessToken = response?.accessToken || null;
286
+ this.tokenExpiry = response?.expiresOn ? new Date(response.expiresOn).getTime() : null;
287
+ this.scopes = allScopes;
288
+ await this.saveTokenCache();
289
+ return true;
290
+ }
291
+ catch (error) {
292
+ logger.error(`Error expanding to work account scopes: ${error.message}`);
293
+ return false;
294
+ }
295
+ }
296
+ requiresWorkAccountScope(toolName) {
297
+ const endpoint = endpoints.default.find((e) => e.toolName === toolName);
298
+ return endpoint?.requiresWorkAccount === true;
299
+ }
229
300
  }
230
301
  export default AuthManager;
302
+ export { buildScopesFromEndpoints, buildAllScopes };
package/dist/cli.js CHANGED
@@ -17,12 +17,16 @@ program
17
17
  .option('--verify-login', 'Verify login without starting the server')
18
18
  .option('--read-only', 'Start server in read-only mode, disabling write operations')
19
19
  .option('--http [port]', 'Use Streamable HTTP transport instead of stdio (optionally specify port, default: 3000)')
20
- .option('--enable-auth-tools', 'Enable login/logout tools when using HTTP mode (disabled by default in HTTP mode)');
20
+ .option('--enable-auth-tools', 'Enable login/logout tools when using HTTP mode (disabled by default in HTTP mode)')
21
+ .option('--enabled-tools <pattern>', 'Filter tools using regex pattern (e.g., "excel|contact" to enable Excel and Contact tools)');
21
22
  export function parseArgs() {
22
23
  program.parse();
23
24
  const options = program.opts();
24
25
  if (process.env.READ_ONLY === 'true' || process.env.READ_ONLY === '1') {
25
26
  options.readOnly = true;
26
27
  }
28
+ if (process.env.ENABLED_TOOLS) {
29
+ options.enabledTools = process.env.ENABLED_TOOLS;
30
+ }
27
31
  return options;
28
32
  }
@@ -279,5 +279,110 @@
279
279
  "method": "get",
280
280
  "toolName": "get-current-user",
281
281
  "scopes": ["User.Read"]
282
+ },
283
+ {
284
+ "pathPattern": "/me/chats",
285
+ "method": "get",
286
+ "toolName": "list-chats",
287
+ "scopes": ["Chat.Read"],
288
+ "requiresWorkAccount": true
289
+ },
290
+ {
291
+ "pathPattern": "/chats/{chat-id}",
292
+ "method": "get",
293
+ "toolName": "get-chat",
294
+ "scopes": ["Chat.Read"],
295
+ "requiresWorkAccount": true
296
+ },
297
+ {
298
+ "pathPattern": "/chats/{chat-id}/messages",
299
+ "method": "get",
300
+ "toolName": "list-chat-messages",
301
+ "scopes": ["ChatMessage.Read"],
302
+ "requiresWorkAccount": true
303
+ },
304
+ {
305
+ "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}",
306
+ "method": "get",
307
+ "toolName": "get-chat-message",
308
+ "scopes": ["ChatMessage.Read"],
309
+ "requiresWorkAccount": true
310
+ },
311
+ {
312
+ "pathPattern": "/chats/{chat-id}/messages",
313
+ "method": "post",
314
+ "toolName": "send-chat-message",
315
+ "scopes": ["ChatMessage.Send"],
316
+ "requiresWorkAccount": true
317
+ },
318
+ {
319
+ "pathPattern": "/me/joinedTeams",
320
+ "method": "get",
321
+ "toolName": "list-joined-teams",
322
+ "scopes": ["Team.ReadBasic.All"],
323
+ "requiresWorkAccount": true
324
+ },
325
+ {
326
+ "pathPattern": "/teams/{team-id}",
327
+ "method": "get",
328
+ "toolName": "get-team",
329
+ "scopes": ["Team.ReadBasic.All"],
330
+ "requiresWorkAccount": true
331
+ },
332
+ {
333
+ "pathPattern": "/teams/{team-id}/channels",
334
+ "method": "get",
335
+ "toolName": "list-team-channels",
336
+ "scopes": ["Channel.ReadBasic.All"],
337
+ "requiresWorkAccount": true
338
+ },
339
+ {
340
+ "pathPattern": "/teams/{team-id}/channels/{channel-id}",
341
+ "method": "get",
342
+ "toolName": "get-team-channel",
343
+ "scopes": ["Channel.ReadBasic.All"],
344
+ "requiresWorkAccount": true
345
+ },
346
+ {
347
+ "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages",
348
+ "method": "get",
349
+ "toolName": "list-channel-messages",
350
+ "scopes": ["ChannelMessage.Read.All"],
351
+ "requiresWorkAccount": true
352
+ },
353
+ {
354
+ "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}",
355
+ "method": "get",
356
+ "toolName": "get-channel-message",
357
+ "scopes": ["ChannelMessage.Read.All"],
358
+ "requiresWorkAccount": true
359
+ },
360
+ {
361
+ "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages",
362
+ "method": "post",
363
+ "toolName": "send-channel-message",
364
+ "scopes": ["ChannelMessage.Send"],
365
+ "requiresWorkAccount": true
366
+ },
367
+ {
368
+ "pathPattern": "/teams/{team-id}/members",
369
+ "method": "get",
370
+ "toolName": "list-team-members",
371
+ "scopes": ["TeamMember.Read.All"],
372
+ "requiresWorkAccount": true
373
+ },
374
+ {
375
+ "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/replies",
376
+ "method": "get",
377
+ "toolName": "list-chat-message-replies",
378
+ "scopes": ["ChatMessage.Read"],
379
+ "requiresWorkAccount": true
380
+ },
381
+ {
382
+ "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/replies",
383
+ "method": "post",
384
+ "toolName": "reply-to-chat-message",
385
+ "scopes": ["ChatMessage.Send"],
386
+ "requiresWorkAccount": true
282
387
  }
283
388
  ]