@softeria/ms-365-mcp-server 0.13.3 → 0.14.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.
@@ -34,43 +34,6 @@ class GraphClient {
34
34
  const accountSessions = this.getAccountSessions(accountId);
35
35
  accountSessions.set(filePath, sessionId);
36
36
  }
37
- async createSession(filePath) {
38
- try {
39
- if (!filePath) {
40
- logger.error("No file path provided for Excel session");
41
- return null;
42
- }
43
- const existingSession = await this.getSessionForFile(filePath);
44
- if (existingSession) {
45
- return existingSession;
46
- }
47
- logger.info(`Creating new Excel session for file: ${filePath}`);
48
- const accessToken = await this.authManager.getToken();
49
- const response = await fetch(
50
- `https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/createSession`,
51
- {
52
- method: "POST",
53
- headers: {
54
- Authorization: `Bearer ${accessToken}`,
55
- "Content-Type": "application/json"
56
- },
57
- body: JSON.stringify({ persistChanges: true })
58
- }
59
- );
60
- if (!response.ok) {
61
- const errorText = await response.text();
62
- logger.error(`Failed to create session: ${response.status} - ${errorText}`);
63
- return null;
64
- }
65
- const result = await response.json();
66
- logger.info(`Session created successfully for file: ${filePath}`);
67
- await this.setSessionForFile(filePath, result.id);
68
- return result.id;
69
- } catch (error) {
70
- logger.error(`Error creating Excel session: ${error}`);
71
- return null;
72
- }
73
- }
74
37
  async makeRequest(endpoint, options = {}) {
75
38
  let accessToken = options.accessToken || this.accessToken || await this.authManager.getToken();
76
39
  let refreshToken = options.refreshToken || this.refreshToken;
@@ -99,7 +62,9 @@ class GraphClient {
99
62
  );
100
63
  }
101
64
  if (!response.ok) {
102
- throw new Error(`Microsoft Graph API error: ${response.status} ${response.statusText}`);
65
+ throw new Error(
66
+ `Microsoft Graph API error: ${response.status} ${response.statusText} - ${await response.text()}`
67
+ );
103
68
  }
104
69
  const text = await response.text();
105
70
  if (text === "") {
@@ -137,7 +102,7 @@ class GraphClient {
137
102
  sessionId = await this.createSessionWithToken(options.excelFile, accessToken);
138
103
  }
139
104
  url = `https://graph.microsoft.com/v1.0/me/drive/root:${options.excelFile}:${endpoint}`;
140
- } else if (endpoint.startsWith("/drive") || endpoint.startsWith("/users") || endpoint.startsWith("/me") || endpoint.startsWith("/teams") || endpoint.startsWith("/chats") || endpoint.startsWith("/planner")) {
105
+ } else if (endpoint.startsWith("/drive") || endpoint.startsWith("/users") || endpoint.startsWith("/me") || endpoint.startsWith("/teams") || endpoint.startsWith("/chats") || endpoint.startsWith("/planner") || endpoint.startsWith("/search")) {
141
106
  url = `https://graph.microsoft.com/v1.0${endpoint}`;
142
107
  } else {
143
108
  throw new Error("Excel operation requested without specifying a file");
@@ -230,231 +195,6 @@ class GraphClient {
230
195
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
231
196
  };
232
197
  }
233
- async graphRequestOld(endpoint, options = {}) {
234
- try {
235
- logger.info(`Calling ${endpoint} with options: ${JSON.stringify(options)}`);
236
- let accessToken = await this.authManager.getToken();
237
- let url;
238
- let sessionId = null;
239
- if (options.excelFile && !endpoint.startsWith("/drive") && !endpoint.startsWith("/users") && !endpoint.startsWith("/me") && !endpoint.startsWith("/teams") && !endpoint.startsWith("/chats") && !endpoint.startsWith("/planner") && !endpoint.startsWith("/sites")) {
240
- sessionId = await this.getSessionForFile(options.excelFile);
241
- if (!sessionId) {
242
- sessionId = await this.createSession(options.excelFile);
243
- }
244
- url = `https://graph.microsoft.com/v1.0/me/drive/root:${options.excelFile}:${endpoint}`;
245
- } else if (endpoint.startsWith("/drive") || endpoint.startsWith("/users") || endpoint.startsWith("/me") || endpoint.startsWith("/teams") || endpoint.startsWith("/chats") || endpoint.startsWith("/planner") || endpoint.startsWith("/sites")) {
246
- url = `https://graph.microsoft.com/v1.0${endpoint}`;
247
- } else {
248
- logger.error("Excel operation requested without specifying a file");
249
- return {
250
- content: [
251
- {
252
- type: "text",
253
- text: JSON.stringify({ error: "No Excel file specified for this operation" })
254
- }
255
- ]
256
- };
257
- }
258
- const headers = {
259
- Authorization: `Bearer ${accessToken}`,
260
- "Content-Type": "application/json",
261
- ...sessionId && { "workbook-session-id": sessionId },
262
- ...options.headers
263
- };
264
- delete options.headers;
265
- logger.info(` ** Making request to ${url} with options: ${JSON.stringify(options)}`);
266
- const response = await fetch(url, {
267
- headers,
268
- ...options
269
- });
270
- if (response.status === 401) {
271
- logger.info("Access token expired, refreshing...");
272
- const newToken = await this.authManager.getToken(true);
273
- if (options.excelFile && !endpoint.startsWith("/drive") && !endpoint.startsWith("/users") && !endpoint.startsWith("/me") && !endpoint.startsWith("/teams") && !endpoint.startsWith("/chats") && !endpoint.startsWith("/planner") && !endpoint.startsWith("/sites")) {
274
- sessionId = await this.createSession(options.excelFile);
275
- }
276
- headers.Authorization = `Bearer ${newToken}`;
277
- if (sessionId && !endpoint.startsWith("/drive") && !endpoint.startsWith("/users") && !endpoint.startsWith("/me") && !endpoint.startsWith("/teams") && !endpoint.startsWith("/chats") && !endpoint.startsWith("/planner") && !endpoint.startsWith("/sites")) {
278
- headers["workbook-session-id"] = sessionId;
279
- }
280
- const retryResponse = await fetch(url, {
281
- headers,
282
- ...options
283
- });
284
- if (!retryResponse.ok) {
285
- throw new Error(`Graph API error: ${retryResponse.status} ${await retryResponse.text()}`);
286
- }
287
- return this.formatResponse(retryResponse, options.rawResponse);
288
- }
289
- if (!response.ok) {
290
- throw new Error(`Graph API error: ${response.status} ${await response.text()}`);
291
- }
292
- return this.formatResponse(response, options.rawResponse);
293
- } catch (error) {
294
- logger.error(`Error in Graph API request: ${error}`);
295
- return {
296
- content: [{ type: "text", text: JSON.stringify({ error: error.message }) }],
297
- isError: true
298
- };
299
- }
300
- }
301
- async formatResponse(response, rawResponse = false) {
302
- try {
303
- if (response.status === 204) {
304
- return {
305
- content: [
306
- {
307
- type: "text",
308
- text: JSON.stringify({
309
- message: "Operation completed successfully"
310
- })
311
- }
312
- ]
313
- };
314
- }
315
- if (rawResponse) {
316
- const contentType2 = response.headers.get("content-type");
317
- if (contentType2 && contentType2.startsWith("text/")) {
318
- const text = await response.text();
319
- return {
320
- content: [{ type: "text", text }]
321
- };
322
- }
323
- return {
324
- content: [
325
- {
326
- type: "text",
327
- text: JSON.stringify({
328
- message: "Binary file content received",
329
- contentType: contentType2,
330
- contentLength: response.headers.get("content-length")
331
- })
332
- }
333
- ]
334
- };
335
- }
336
- const contentType = response.headers.get("content-type");
337
- if (contentType && !contentType.includes("application/json")) {
338
- if (contentType.startsWith("text/")) {
339
- const text = await response.text();
340
- return {
341
- content: [{ type: "text", text }]
342
- };
343
- }
344
- return {
345
- content: [
346
- {
347
- type: "text",
348
- text: JSON.stringify({
349
- message: "Binary or non-JSON content received",
350
- contentType,
351
- contentLength: response.headers.get("content-length")
352
- })
353
- }
354
- ]
355
- };
356
- }
357
- const result = await response.json();
358
- const removeODataProps = (obj) => {
359
- if (!obj || typeof obj !== "object") return;
360
- if (Array.isArray(obj)) {
361
- obj.forEach((item) => removeODataProps(item));
362
- } else {
363
- Object.keys(obj).forEach((key) => {
364
- const objRecord = obj;
365
- if (key.startsWith("@odata") && !["@odata.nextLink", "@odata.count"].includes(key)) {
366
- delete objRecord[key];
367
- } else if (typeof objRecord[key] === "object") {
368
- removeODataProps(objRecord[key]);
369
- }
370
- });
371
- }
372
- };
373
- removeODataProps(result);
374
- return {
375
- content: [{ type: "text", text: JSON.stringify(result) }]
376
- };
377
- } catch (error) {
378
- logger.error(`Error formatting response: ${error}`);
379
- return {
380
- content: [{ type: "text", text: JSON.stringify({ message: "Success" }) }]
381
- };
382
- }
383
- }
384
- async closeSession(filePath) {
385
- const sessionId = await this.getSessionForFile(filePath);
386
- if (!filePath || !sessionId) {
387
- return {
388
- content: [
389
- {
390
- type: "text",
391
- text: JSON.stringify({ message: "No active session for the specified file" })
392
- }
393
- ]
394
- };
395
- }
396
- try {
397
- const accessToken = await this.authManager.getToken();
398
- const response = await fetch(
399
- `https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/closeSession`,
400
- {
401
- method: "POST",
402
- headers: {
403
- Authorization: `Bearer ${accessToken}`,
404
- "Content-Type": "application/json",
405
- "workbook-session-id": sessionId
406
- }
407
- }
408
- );
409
- if (response.ok) {
410
- const accountId = await this.getCurrentAccountId();
411
- if (accountId) {
412
- const accountSessions = this.getAccountSessions(accountId);
413
- accountSessions.delete(filePath);
414
- }
415
- return {
416
- content: [
417
- {
418
- type: "text",
419
- text: JSON.stringify({ message: `Session for ${filePath} closed successfully` })
420
- }
421
- ]
422
- };
423
- } else {
424
- throw new Error(`Failed to close session: ${response.status}`);
425
- }
426
- } catch (error) {
427
- logger.error(`Error closing session: ${error}`);
428
- return {
429
- content: [
430
- {
431
- type: "text",
432
- text: JSON.stringify({ error: `Failed to close session for ${filePath}` })
433
- }
434
- ],
435
- isError: true
436
- };
437
- }
438
- }
439
- async closeAllSessions() {
440
- const results = [];
441
- const accountId = await this.getCurrentAccountId();
442
- if (accountId) {
443
- const accountSessions = this.getAccountSessions(accountId);
444
- for (const [filePath] of accountSessions) {
445
- const result = await this.closeSession(filePath);
446
- results.push(result);
447
- }
448
- }
449
- return {
450
- content: [
451
- {
452
- type: "text",
453
- text: JSON.stringify({ message: "All sessions closed", results })
454
- }
455
- ]
456
- };
457
- }
458
198
  }
459
199
  var graph_client_default = GraphClient;
460
200
  export {
@@ -21,7 +21,7 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
21
21
  }
22
22
  for (const tool of api.endpoints) {
23
23
  const endpointConfig = endpointsData.find((e) => e.toolName === tool.alias);
24
- if (endpointConfig?.requiresWorkAccount && !orgMode) {
24
+ if (!orgMode && endpointConfig && !endpointConfig.scopes && endpointConfig.workScopes) {
25
25
  logger.info(`Skipping work account tool ${tool.alias} - not in org mode`);
26
26
  continue;
27
27
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
- "version": "0.13.3",
4
- "description": "Microsoft 365 MCP Server",
3
+ "version": "0.14.0",
4
+ "description": " A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -19,7 +19,7 @@
19
19
  "format:check": "prettier --check \"**/*.{ts,mts,js,mjs,json,md}\"",
20
20
  "lint": "eslint .",
21
21
  "lint:fix": "eslint . --fix",
22
- "verify": "npm run lint && npm run format:check && npm run build && npm run test",
22
+ "verify": "npm run generate && npm run lint && npm run format:check && npm run build && npm run test",
23
23
  "inspector": "npx @modelcontextprotocol/inspector tsx src/index.ts"
24
24
  },
25
25
  "keywords": [
@@ -314,189 +314,169 @@
314
314
  "pathPattern": "/me/chats",
315
315
  "method": "get",
316
316
  "toolName": "list-chats",
317
- "scopes": ["Chat.Read"],
318
- "requiresWorkAccount": true
317
+ "workScopes": ["Chat.Read"]
319
318
  },
320
319
  {
321
320
  "pathPattern": "/chats/{chat-id}",
322
321
  "method": "get",
323
322
  "toolName": "get-chat",
324
- "scopes": ["Chat.Read"],
325
- "requiresWorkAccount": true
323
+ "workScopes": ["Chat.Read"]
326
324
  },
327
325
  {
328
326
  "pathPattern": "/chats/{chat-id}/messages",
329
327
  "method": "get",
330
328
  "toolName": "list-chat-messages",
331
- "scopes": ["ChatMessage.Read"],
332
- "requiresWorkAccount": true
329
+ "workScopes": ["ChatMessage.Read"]
333
330
  },
334
331
  {
335
332
  "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}",
336
333
  "method": "get",
337
334
  "toolName": "get-chat-message",
338
- "scopes": ["ChatMessage.Read"],
339
- "requiresWorkAccount": true
335
+ "workScopes": ["ChatMessage.Read"]
340
336
  },
341
337
  {
342
338
  "pathPattern": "/chats/{chat-id}/messages",
343
339
  "method": "post",
344
340
  "toolName": "send-chat-message",
345
- "scopes": ["ChatMessage.Send"],
346
- "requiresWorkAccount": true
341
+ "workScopes": ["ChatMessage.Send"]
347
342
  },
348
343
  {
349
344
  "pathPattern": "/me/joinedTeams",
350
345
  "method": "get",
351
346
  "toolName": "list-joined-teams",
352
- "scopes": ["Team.ReadBasic.All"],
353
- "requiresWorkAccount": true
347
+ "workScopes": ["Team.ReadBasic.All"]
354
348
  },
355
349
  {
356
350
  "pathPattern": "/teams/{team-id}",
357
351
  "method": "get",
358
352
  "toolName": "get-team",
359
- "scopes": ["Team.ReadBasic.All"],
360
- "requiresWorkAccount": true
353
+ "workScopes": ["Team.ReadBasic.All"]
361
354
  },
362
355
  {
363
356
  "pathPattern": "/teams/{team-id}/channels",
364
357
  "method": "get",
365
358
  "toolName": "list-team-channels",
366
- "scopes": ["Channel.ReadBasic.All"],
367
- "requiresWorkAccount": true
359
+ "workScopes": ["Channel.ReadBasic.All"]
368
360
  },
369
361
  {
370
362
  "pathPattern": "/teams/{team-id}/channels/{channel-id}",
371
363
  "method": "get",
372
364
  "toolName": "get-team-channel",
373
- "scopes": ["Channel.ReadBasic.All"],
374
- "requiresWorkAccount": true
365
+ "workScopes": ["Channel.ReadBasic.All"]
375
366
  },
376
367
  {
377
368
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages",
378
369
  "method": "get",
379
370
  "toolName": "list-channel-messages",
380
- "scopes": ["ChannelMessage.Read.All"],
381
- "requiresWorkAccount": true
371
+ "workScopes": ["ChannelMessage.Read.All"]
382
372
  },
383
373
  {
384
374
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}",
385
375
  "method": "get",
386
376
  "toolName": "get-channel-message",
387
- "scopes": ["ChannelMessage.Read.All"],
388
- "requiresWorkAccount": true
377
+ "workScopes": ["ChannelMessage.Read.All"]
389
378
  },
390
379
  {
391
380
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages",
392
381
  "method": "post",
393
382
  "toolName": "send-channel-message",
394
- "scopes": ["ChannelMessage.Send"],
395
- "requiresWorkAccount": true
383
+ "workScopes": ["ChannelMessage.Send"]
396
384
  },
397
385
  {
398
386
  "pathPattern": "/teams/{team-id}/members",
399
387
  "method": "get",
400
388
  "toolName": "list-team-members",
401
- "scopes": ["TeamMember.Read.All"],
402
- "requiresWorkAccount": true
389
+ "workScopes": ["TeamMember.Read.All"]
403
390
  },
404
391
  {
405
392
  "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/replies",
406
393
  "method": "get",
407
394
  "toolName": "list-chat-message-replies",
408
- "scopes": ["ChatMessage.Read"],
409
- "requiresWorkAccount": true
395
+ "workScopes": ["ChatMessage.Read"]
410
396
  },
411
397
  {
412
398
  "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/replies",
413
399
  "method": "post",
414
400
  "toolName": "reply-to-chat-message",
415
- "scopes": ["ChatMessage.Send"],
416
- "requiresWorkAccount": true
401
+ "workScopes": ["ChatMessage.Send"]
417
402
  },
418
403
  {
419
404
  "pathPattern": "/sites",
420
405
  "method": "get",
421
406
  "toolName": "search-sharepoint-sites",
422
- "scopes": ["Sites.Read.All"],
423
- "requiresWorkAccount": true
407
+ "workScopes": ["Sites.Read.All"]
424
408
  },
425
409
  {
426
410
  "pathPattern": "/sites/{site-id}",
427
411
  "method": "get",
428
412
  "toolName": "get-sharepoint-site",
429
- "scopes": ["Sites.Read.All"],
430
- "requiresWorkAccount": true
413
+ "workScopes": ["Sites.Read.All"]
431
414
  },
432
415
  {
433
416
  "pathPattern": "/sites/{site-id}/drives",
434
417
  "method": "get",
435
418
  "toolName": "list-sharepoint-site-drives",
436
- "scopes": ["Sites.Read.All"],
437
- "requiresWorkAccount": true
419
+ "workScopes": ["Sites.Read.All"]
438
420
  },
439
421
  {
440
422
  "pathPattern": "/sites/{site-id}/drives/{drive-id}",
441
423
  "method": "get",
442
424
  "toolName": "get-sharepoint-site-drive-by-id",
443
- "scopes": ["Sites.Read.All"],
444
- "requiresWorkAccount": true
425
+ "workScopes": ["Sites.Read.All"]
445
426
  },
446
427
  {
447
428
  "pathPattern": "/sites/{site-id}/items",
448
429
  "method": "get",
449
430
  "toolName": "list-sharepoint-site-items",
450
- "scopes": ["Sites.Read.All"],
451
- "requiresWorkAccount": true
431
+ "workScopes": ["Sites.Read.All"]
452
432
  },
453
433
  {
454
434
  "pathPattern": "/sites/{site-id}/items/{baseItem-id}",
455
435
  "method": "get",
456
436
  "toolName": "get-sharepoint-site-item",
457
- "scopes": ["Sites.Read.All"],
458
- "requiresWorkAccount": true
437
+ "workScopes": ["Sites.Read.All"]
459
438
  },
460
439
  {
461
440
  "pathPattern": "/sites/{site-id}/lists",
462
441
  "method": "get",
463
442
  "toolName": "list-sharepoint-site-lists",
464
- "scopes": ["Sites.Read.All"],
465
- "requiresWorkAccount": true
443
+ "workScopes": ["Sites.Read.All"]
466
444
  },
467
445
  {
468
446
  "pathPattern": "/sites/{site-id}/lists/{list-id}",
469
447
  "method": "get",
470
448
  "toolName": "get-sharepoint-site-list",
471
- "scopes": ["Sites.Read.All"],
472
- "requiresWorkAccount": true
449
+ "workScopes": ["Sites.Read.All"]
473
450
  },
474
451
  {
475
452
  "pathPattern": "/sites/{site-id}/lists/{list-id}/items",
476
453
  "method": "get",
477
454
  "toolName": "list-sharepoint-site-list-items",
478
- "scopes": ["Sites.Read.All"],
479
- "requiresWorkAccount": true
455
+ "workScopes": ["Sites.Read.All"]
480
456
  },
481
457
  {
482
458
  "pathPattern": "/sites/{site-id}/lists/{list-id}/items/{listItem-id}",
483
459
  "method": "get",
484
460
  "toolName": "get-sharepoint-site-list-item",
485
- "scopes": ["Sites.Read.All"],
486
- "requiresWorkAccount": true
461
+ "workScopes": ["Sites.Read.All"]
487
462
  },
488
463
  {
489
464
  "pathPattern": "/sites/{site-id}/getByPath(path='{path}')",
490
465
  "method": "get",
491
466
  "toolName": "get-sharepoint-site-by-path",
492
- "scopes": ["Sites.Read.All"],
493
- "requiresWorkAccount": true
467
+ "workScopes": ["Sites.Read.All"]
494
468
  },
495
469
  {
496
470
  "pathPattern": "/sites/delta()",
497
471
  "method": "get",
498
472
  "toolName": "get-sharepoint-sites-delta",
499
- "scopes": ["Sites.Read.All"],
500
- "requiresWorkAccount": true
473
+ "workScopes": ["Sites.Read.All"]
474
+ },
475
+ {
476
+ "pathPattern": "/search/query",
477
+ "method": "post",
478
+ "toolName": "search-query",
479
+ "scopes": ["Mail.Read", "Calendars.Read", "Files.Read.All", "People.Read"],
480
+ "workScopes": ["Sites.Read.All", "Chat.Read", "ChannelMessage.Read.All"]
501
481
  }
502
482
  ]