@softeria/ms-365-mcp-server 0.45.2 → 0.46.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 CHANGED
@@ -96,9 +96,10 @@ MS365_MCP_OUTPUT_FORMAT=toon npx @softeria/ms-365-mcp-server
96
96
 
97
97
  ### Personal Account Tools (Available by default)
98
98
 
99
- **Email (Outlook)**
100
- <sub>list-mail-messages, list-mail-folders, list-mail-folder-messages, get-mail-message, send-mail,
101
- delete-mail-message, create-draft-email, move-mail-message</sub>
99
+ **Email (Outlook)**
100
+ <sub>list-mail-messages, list-mail-folders, list-mail-child-folders, list-mail-folder-messages, get-mail-message, send-mail,
101
+ delete-mail-message, create-draft-email, move-mail-message, create-mail-folder, create-mail-child-folder,
102
+ update-mail-folder, delete-mail-folder</sub>
102
103
 
103
104
  **Calendar**
104
105
  <sub>list-calendars, list-calendar-events, get-calendar-event, get-calendar-view, create-calendar-event,
package/SECURITY.md ADDED
@@ -0,0 +1,11 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you discover a security vulnerability in this project, please report it
6
+ responsibly through **GitHub Private Vulnerability Reporting (PVR)**.
7
+
8
+ **To report**: Go to the [Security Advisories page](../../security/advisories/new)
9
+ and submit a new advisory.
10
+
11
+ Please **do not** open public issues for security vulnerabilities.
@@ -18,6 +18,34 @@
18
18
  "toolName": "list-mail-child-folders",
19
19
  "scopes": ["Mail.Read"]
20
20
  },
21
+ {
22
+ "pathPattern": "/me/mailFolders",
23
+ "method": "post",
24
+ "toolName": "create-mail-folder",
25
+ "scopes": ["Mail.ReadWrite"],
26
+ "llmTip": "Creates a top-level mail folder. Use create-mail-child-folder to create a subfolder inside an existing folder. Use list-mail-folders to find existing folder IDs."
27
+ },
28
+ {
29
+ "pathPattern": "/me/mailFolders/{mailFolder-id}/childFolders",
30
+ "method": "post",
31
+ "toolName": "create-mail-child-folder",
32
+ "scopes": ["Mail.ReadWrite"],
33
+ "llmTip": "Creates a subfolder inside an existing mail folder. Use list-mail-folders or list-mail-child-folders to find the parent folder ID."
34
+ },
35
+ {
36
+ "pathPattern": "/me/mailFolders/{mailFolder-id}",
37
+ "method": "patch",
38
+ "toolName": "update-mail-folder",
39
+ "scopes": ["Mail.ReadWrite"],
40
+ "llmTip": "Renames a mail folder by updating its displayName. Use list-mail-folders to find the folder ID."
41
+ },
42
+ {
43
+ "pathPattern": "/me/mailFolders/{mailFolder-id}",
44
+ "method": "delete",
45
+ "toolName": "delete-mail-folder",
46
+ "scopes": ["Mail.ReadWrite"],
47
+ "llmTip": "Deletes a mail folder and all its contents. This action is irreversible. Use list-mail-folders to find the folder ID."
48
+ },
21
49
  {
22
50
  "pathPattern": "/me/mailFolders/{mailFolder-id}/messages",
23
51
  "method": "get",
@@ -245,7 +273,7 @@
245
273
  "scopes": ["Calendars.Read"],
246
274
  "supportsTimezone": true,
247
275
  "supportsExpandExtendedProperties": true,
248
- "llmTip": "Returns expanded recurring event instances (not just seriesMaster) within a date range for the default calendar. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Use get-specific-calendar-view if you need a non-default calendar."
276
+ "llmTip": "Returns expanded recurring event instances (not just seriesMaster) within a date range for the default calendar. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Use get-specific-calendar-view if you need a non-default calendar. To find Teams meetings, use $filter=isOnlineMeeting eq true. To search by subject, use $filter=contains(subject,'keyword'). Teams meetings include a joinWebUrl property needed for transcript access via list-online-meetings."
249
277
  },
250
278
  {
251
279
  "pathPattern": "/me/calendars/{calendar-id}/calendarView",
@@ -254,7 +282,7 @@
254
282
  "scopes": ["Calendars.Read"],
255
283
  "supportsTimezone": true,
256
284
  "supportsExpandExtendedProperties": true,
257
- "llmTip": "Returns expanded recurring event instances (not just seriesMaster) within a date range for a specific calendar. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Each instance includes seriesMasterId and type (occurrence/exception) fields for recurring event linkage. Use fetchAllPages=true to retrieve all results when there are many events."
285
+ "llmTip": "Returns expanded recurring event instances (not just seriesMaster) within a date range for a specific calendar. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Each instance includes seriesMasterId and type (occurrence/exception) fields for recurring event linkage. Use fetchAllPages=true to retrieve all results when there are many events. To find Teams meetings, use $filter=isOnlineMeeting eq true. Teams meetings include a joinWebUrl property needed for transcript access via list-online-meetings."
258
286
  },
259
287
  {
260
288
  "pathPattern": "/me/calendars/{calendar-id}/events/{event-id}/instances",
@@ -466,6 +494,12 @@
466
494
  "toolName": "update-planner-task",
467
495
  "scopes": ["Tasks.ReadWrite"]
468
496
  },
497
+ {
498
+ "pathPattern": "/planner/tasks/{plannerTask-id}/details",
499
+ "method": "get",
500
+ "toolName": "get-planner-task-details",
501
+ "scopes": ["Tasks.Read"]
502
+ },
469
503
  {
470
504
  "pathPattern": "/planner/tasks/{plannerTask-id}/details",
471
505
  "method": "patch",
@@ -693,6 +727,7 @@
693
727
  "pathPattern": "/me/onlineMeetings",
694
728
  "method": "get",
695
729
  "toolName": "list-online-meetings",
730
+ "scopes": ["OnlineMeetings.Read"],
696
731
  "workScopes": ["OnlineMeetings.Read"],
697
732
  "llmTip": "List online meetings. Use $filter=joinWebUrl eq '{url}' to find a specific meeting by its Teams join link. Returns meeting IDs needed for transcript access."
698
733
  },
@@ -700,6 +735,7 @@
700
735
  "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/transcripts",
701
736
  "method": "get",
702
737
  "toolName": "list-meeting-transcripts",
738
+ "scopes": ["OnlineMeetingTranscript.Read.All"],
703
739
  "workScopes": ["OnlineMeetingTranscript.Read.All"],
704
740
  "llmTip": "Lists available transcripts for a meeting. Get the meeting ID first via list-online-meetings."
705
741
  },
@@ -707,10 +743,26 @@
707
743
  "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/transcripts/{callTranscript-id}/content",
708
744
  "method": "get",
709
745
  "toolName": "get-meeting-transcript-content",
746
+ "scopes": ["OnlineMeetingTranscript.Read.All"],
710
747
  "workScopes": ["OnlineMeetingTranscript.Read.All"],
711
748
  "acceptType": "text/vtt",
712
749
  "llmTip": "Returns the transcript content in WebVTT format with speaker identification and timestamps."
713
750
  },
751
+ {
752
+ "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/recordings",
753
+ "method": "get",
754
+ "toolName": "list-meeting-recordings",
755
+ "workScopes": ["OnlineMeetingRecording.Read.All"],
756
+ "llmTip": "Lists recordings for a meeting. Each recording has createdDateTime, endDateTime, meetingOrganizer, contentCorrelationId (links to corresponding transcript). For recurring meetings, there is one recording per occurrence."
757
+ },
758
+ {
759
+ "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/recordings/{callRecording-id}/content",
760
+ "method": "get",
761
+ "toolName": "get-meeting-recording-content",
762
+ "workScopes": ["OnlineMeetingRecording.Read.All"],
763
+ "returnDownloadUrl": true,
764
+ "llmTip": "Returns a temporary download URL for the meeting recording in MP4 format. Use the download URL to access the video file. The recording may be large — do not attempt to stream it inline."
765
+ },
714
766
  {
715
767
  "pathPattern": "/groups/{group-id}/conversations",
716
768
  "method": "get",
@@ -2873,6 +2873,32 @@ const microsoft_graph_onlineMeetingCollectionResponse = z.object({
2873
2873
  "@odata.nextLink": z.string().nullable(),
2874
2874
  value: z.array(microsoft_graph_onlineMeeting)
2875
2875
  }).partial().passthrough();
2876
+ const microsoft_graph_callRecording = z.object({
2877
+ id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
2878
+ callId: z.string().describe("The unique identifier for the call that is related to this recording. Read-only.").nullish(),
2879
+ content: z.string().describe("The content of the recording. Read-only.").nullish(),
2880
+ contentCorrelationId: z.string().describe(
2881
+ "The unique identifier that links the transcript with its corresponding recording. Read-only."
2882
+ ).nullish(),
2883
+ createdDateTime: z.string().regex(
2884
+ /^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$/
2885
+ ).datetime({ offset: true }).describe(
2886
+ "Date and time at which the recording was created. The timestamp type represents date and time information using ISO 8601 format and is always in UTC. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z. Read-only."
2887
+ ).nullish(),
2888
+ endDateTime: z.string().regex(
2889
+ /^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$/
2890
+ ).datetime({ offset: true }).describe(
2891
+ "Date and time at which the recording ends. The timestamp type represents date and time information using ISO 8601 format and is always in UTC. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z. Read-only."
2892
+ ).nullish(),
2893
+ meetingId: z.string().describe("The unique identifier of the onlineMeeting related to this recording. Read-only.").nullish(),
2894
+ meetingOrganizer: microsoft_graph_identitySet.optional(),
2895
+ recordingContentUrl: z.string().describe("The URL that can be used to access the content of the recording. Read-only.").nullish()
2896
+ }).passthrough();
2897
+ const microsoft_graph_callRecordingCollectionResponse = z.object({
2898
+ "@odata.count": z.number().int().nullable(),
2899
+ "@odata.nextLink": z.string().nullable(),
2900
+ value: z.array(microsoft_graph_callRecording)
2901
+ }).partial().passthrough();
2876
2902
  const microsoft_graph_callTranscript = z.object({
2877
2903
  id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
2878
2904
  callId: z.string().describe("The unique identifier for the call that is related to this transcript. Read-only.").nullish(),
@@ -3752,6 +3778,8 @@ const schemas = {
3752
3778
  microsoft_graph_joinMeetingIdSettings,
3753
3779
  microsoft_graph_onlineMeeting,
3754
3780
  microsoft_graph_onlineMeetingCollectionResponse,
3781
+ microsoft_graph_callRecording,
3782
+ microsoft_graph_callRecordingCollectionResponse,
3755
3783
  microsoft_graph_callTranscript,
3756
3784
  microsoft_graph_callTranscriptCollectionResponse,
3757
3785
  microsoft_graph_plannerChecklistItems,
@@ -5482,6 +5510,53 @@ Based on this value, you can better adjust the parameters and call findMeetingTi
5482
5510
  ],
5483
5511
  response: z.void()
5484
5512
  },
5513
+ {
5514
+ method: "post",
5515
+ path: "/me/mailFolders",
5516
+ alias: "create-mail-folder",
5517
+ description: `Use this API to create a new mail folder in the root folder of the user's mailbox. If you intend a new folder to be hidden, you must set the isHidden property to true on creation.`,
5518
+ requestFormat: "json",
5519
+ parameters: [
5520
+ {
5521
+ name: "body",
5522
+ description: `New navigation property`,
5523
+ type: "Body",
5524
+ schema: microsoft_graph_mailFolder
5525
+ }
5526
+ ],
5527
+ response: z.void()
5528
+ },
5529
+ {
5530
+ method: "patch",
5531
+ path: "/me/mailFolders/:mailFolderId",
5532
+ alias: "update-mail-folder",
5533
+ description: `Update the properties of mailfolder object.`,
5534
+ requestFormat: "json",
5535
+ parameters: [
5536
+ {
5537
+ name: "body",
5538
+ description: `New navigation property values`,
5539
+ type: "Body",
5540
+ schema: microsoft_graph_mailFolder
5541
+ }
5542
+ ],
5543
+ response: z.void()
5544
+ },
5545
+ {
5546
+ method: "delete",
5547
+ path: "/me/mailFolders/:mailFolderId",
5548
+ alias: "delete-mail-folder",
5549
+ description: `Delete the specified mailFolder. The folder can be a mailSearchFolder. You can specify a mail folder by its folder ID, or by its well-known folder name, if one exists.`,
5550
+ requestFormat: "json",
5551
+ parameters: [
5552
+ {
5553
+ name: "If-Match",
5554
+ type: "Header",
5555
+ schema: z.string().describe("ETag").optional()
5556
+ }
5557
+ ],
5558
+ response: z.void()
5559
+ },
5485
5560
  {
5486
5561
  method: "get",
5487
5562
  path: "/me/mailFolders/:mailFolderId/childFolders",
@@ -5538,6 +5613,22 @@ folder collection and navigate to another folder. By default, this operation doe
5538
5613
  ],
5539
5614
  response: z.void()
5540
5615
  },
5616
+ {
5617
+ method: "post",
5618
+ path: "/me/mailFolders/:mailFolderId/childFolders",
5619
+ alias: "create-mail-child-folder",
5620
+ description: `Use this API to create a new child mailFolder. If you intend a new folder to be hidden, you must set the isHidden property to true on creation.`,
5621
+ requestFormat: "json",
5622
+ parameters: [
5623
+ {
5624
+ name: "body",
5625
+ description: `New navigation property`,
5626
+ type: "Body",
5627
+ schema: microsoft_graph_mailFolder
5628
+ }
5629
+ ],
5630
+ response: z.void()
5631
+ },
5541
5632
  {
5542
5633
  method: "get",
5543
5634
  path: "/me/mailFolders/:mailFolderId/messages",
@@ -5908,12 +5999,7 @@ resource.`,
5908
5999
  method: "post",
5909
6000
  path: "/me/messages/:messageId/createForward",
5910
6001
  alias: "create-forward-draft",
5911
- description: `Create a draft to forward an existing message, in either JSON or MIME format. When using JSON format, you can:
5912
- - Specify either a comment or the body property of the message parameter. Specifying both will return an HTTP 400 Bad Request error.
5913
- - Specify either the toRecipients parameter or the toRecipients property of the message parameter. Specifying both or specifying neither will return an HTTP 400 Bad Request error.
5914
- - Update the draft later to add content to the body or change other message properties. When using MIME format:
5915
- - Provide the applicable Internet message headers and the MIME content, all encoded in base64 format in the request body.
5916
- - Add any attachments and S/MIME properties to the MIME content. Send the draft message in a subsequent operation. Alternatively, forward a message in a single operation.`,
6002
+ description: `Invoke action createForward`,
5917
6003
  requestFormat: "json",
5918
6004
  parameters: [
5919
6005
  {
@@ -6291,6 +6377,64 @@ resource.`,
6291
6377
  ],
6292
6378
  response: z.void()
6293
6379
  },
6380
+ {
6381
+ method: "get",
6382
+ path: "/me/onlineMeetings/:onlineMeetingId/recordings",
6383
+ alias: "list-meeting-recordings",
6384
+ description: `Get a callRecording object associated with a scheduled online meeting and an ad hoc call. This API supports the retrieval of call recordings from all meeting types except live events. For a recording, this API returns the metadata of the single recording associated with the online meeting or an ad hoc call. For the content of a recording, this API returns the stream of bytes associated with the recording.`,
6385
+ requestFormat: "json",
6386
+ parameters: [
6387
+ {
6388
+ name: "$top",
6389
+ type: "Query",
6390
+ schema: z.number().int().gte(0).describe("Show only the first n items").optional()
6391
+ },
6392
+ {
6393
+ name: "$skip",
6394
+ type: "Query",
6395
+ schema: z.number().int().gte(0).describe("Skip the first n items").optional()
6396
+ },
6397
+ {
6398
+ name: "$search",
6399
+ type: "Query",
6400
+ schema: z.string().describe("Search items by search phrases").optional()
6401
+ },
6402
+ {
6403
+ name: "$filter",
6404
+ type: "Query",
6405
+ schema: z.string().describe("Filter items by property values").optional()
6406
+ },
6407
+ {
6408
+ name: "$count",
6409
+ type: "Query",
6410
+ schema: z.boolean().describe("Include count of items").optional()
6411
+ },
6412
+ {
6413
+ name: "$orderby",
6414
+ type: "Query",
6415
+ schema: z.array(z.string()).describe("Order items by property values").optional()
6416
+ },
6417
+ {
6418
+ name: "$select",
6419
+ type: "Query",
6420
+ schema: z.array(z.string()).describe("Select properties to be returned").optional()
6421
+ },
6422
+ {
6423
+ name: "$expand",
6424
+ type: "Query",
6425
+ schema: z.array(z.string()).describe("Expand related entities").optional()
6426
+ }
6427
+ ],
6428
+ response: z.void()
6429
+ },
6430
+ {
6431
+ method: "get",
6432
+ path: "/me/onlineMeetings/:onlineMeetingId/recordings/:callRecordingId/content",
6433
+ alias: "get-meeting-recording-content",
6434
+ description: `The content of the recording. Read-only.`,
6435
+ requestFormat: "json",
6436
+ response: z.void()
6437
+ },
6294
6438
  {
6295
6439
  method: "get",
6296
6440
  path: "/me/onlineMeetings/:onlineMeetingId/transcripts",
@@ -6825,6 +6969,26 @@ resource.`,
6825
6969
  ],
6826
6970
  response: z.void()
6827
6971
  },
6972
+ {
6973
+ method: "get",
6974
+ path: "/planner/tasks/:plannerTaskId/details",
6975
+ alias: "get-planner-task-details",
6976
+ description: `Retrieve the properties and relationships of a plannerTaskDetails object.`,
6977
+ requestFormat: "json",
6978
+ parameters: [
6979
+ {
6980
+ name: "$select",
6981
+ type: "Query",
6982
+ schema: z.array(z.string()).describe("Select properties to be returned").optional()
6983
+ },
6984
+ {
6985
+ name: "$expand",
6986
+ type: "Query",
6987
+ schema: z.array(z.string()).describe("Expand related entities").optional()
6988
+ }
6989
+ ],
6990
+ response: z.void()
6991
+ },
6828
6992
  {
6829
6993
  method: "patch",
6830
6994
  path: "/planner/tasks/:plannerTaskId/details",
@@ -6,6 +6,7 @@ import path from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { TOOL_CATEGORIES } from "./tool-categories.js";
8
8
  import { getRequestTokens } from "./request-context.js";
9
+ import { parseTeamsUrl } from "./lib/teams-url-parser.js";
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
  const __dirname = path.dirname(__filename);
11
12
  const endpointsData = JSON.parse(
@@ -349,6 +350,39 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
349
350
  if (multiAccount) {
350
351
  logger.info('Multi-account mode: "account" parameter injected into all tool schemas');
351
352
  }
353
+ if (!enabledToolsRegex || enabledToolsRegex.test("parse-teams-url")) {
354
+ try {
355
+ server.tool(
356
+ "parse-teams-url",
357
+ "Converts any Teams meeting URL format (short /meet/, full /meetup-join/, or recap ?threadId=) into a standard joinWebUrl. Use this before list-online-meetings when the user provides a recap or short URL.",
358
+ {
359
+ url: z.string().describe("Teams meeting URL in any format")
360
+ },
361
+ {
362
+ title: "parse-teams-url",
363
+ readOnlyHint: true,
364
+ openWorldHint: false
365
+ },
366
+ async ({ url }) => {
367
+ try {
368
+ const joinWebUrl = parseTeamsUrl(url);
369
+ return { content: [{ type: "text", text: joinWebUrl }] };
370
+ } catch (error) {
371
+ return {
372
+ content: [
373
+ { type: "text", text: JSON.stringify({ error: error.message }) }
374
+ ],
375
+ isError: true
376
+ };
377
+ }
378
+ }
379
+ );
380
+ registeredCount++;
381
+ } catch (error) {
382
+ logger.error(`Failed to register tool parse-teams-url: ${error.message}`);
383
+ failedCount++;
384
+ }
385
+ }
352
386
  logger.info(
353
387
  `Tool registration complete: ${registeredCount} registered, ${skippedCount} skipped, ${failedCount} failed`
354
388
  );
@@ -0,0 +1,24 @@
1
+ function parseTeamsUrl(url) {
2
+ if (url.includes("/meet/") || url.includes("/meetup-join/")) {
3
+ return url;
4
+ }
5
+ if (url.toLowerCase().includes("meetingrecap")) {
6
+ const params = Object.fromEntries(
7
+ [...url.matchAll(/([a-zA-Z]+)=([^&#]+)/g)].map((m) => [m[1], m[2]])
8
+ );
9
+ const threadId = decodeURIComponent(params.threadId || "");
10
+ const tenantId = params.tenantId || "";
11
+ const organizerId = params.organizerId || "";
12
+ if (!threadId || !tenantId || !organizerId) {
13
+ throw new Error("Invalid recap URL: missing threadId, tenantId, or organizerId parameter");
14
+ }
15
+ const threadEnc = encodeURIComponent(threadId).replace(/%3A/gi, "%3a").replace(/%40/gi, "%40");
16
+ const ctx = JSON.stringify({ Tid: tenantId, Oid: organizerId });
17
+ const ctxEnc = encodeURIComponent(ctx);
18
+ return `https://teams.microsoft.com/l/meetup-join/${threadEnc}/0?context=${ctxEnc}`;
19
+ }
20
+ return url;
21
+ }
22
+ export {
23
+ parseTeamsUrl
24
+ };
@@ -1,10 +1,10 @@
1
- 2026-03-11 19:05:48 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
2
- 2026-03-11 19:05:48 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
3
- 2026-03-11 19:05:48 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
4
- 2026-03-11 19:05:48 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/messages
5
- 2026-03-11 19:05:48 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/calendar
6
- 2026-03-11 19:05:50 INFO: Using environment variables for secrets
7
- 2026-03-11 19:05:50 INFO: Using environment variables for secrets
8
- 2026-03-11 19:05:50 INFO: Using environment variables for secrets
9
- 2026-03-11 19:05:50 INFO: Using environment variables for secrets
10
- 2026-03-11 19:05:50 INFO: Using environment variables for secrets
1
+ 2026-03-23 20:16:55 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
2
+ 2026-03-23 20:16:55 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
3
+ 2026-03-23 20:16:55 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
4
+ 2026-03-23 20:16:55 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/messages
5
+ 2026-03-23 20:16:55 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/calendar
6
+ 2026-03-23 20:16:56 INFO: Using environment variables for secrets
7
+ 2026-03-23 20:16:56 INFO: Using environment variables for secrets
8
+ 2026-03-23 20:16:56 INFO: Using environment variables for secrets
9
+ 2026-03-23 20:16:56 INFO: Using environment variables for secrets
10
+ 2026-03-23 20:16:56 INFO: Using environment variables for secrets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
- "version": "0.45.2",
3
+ "version": "0.46.0",
4
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",
@@ -18,6 +18,34 @@
18
18
  "toolName": "list-mail-child-folders",
19
19
  "scopes": ["Mail.Read"]
20
20
  },
21
+ {
22
+ "pathPattern": "/me/mailFolders",
23
+ "method": "post",
24
+ "toolName": "create-mail-folder",
25
+ "scopes": ["Mail.ReadWrite"],
26
+ "llmTip": "Creates a top-level mail folder. Use create-mail-child-folder to create a subfolder inside an existing folder. Use list-mail-folders to find existing folder IDs."
27
+ },
28
+ {
29
+ "pathPattern": "/me/mailFolders/{mailFolder-id}/childFolders",
30
+ "method": "post",
31
+ "toolName": "create-mail-child-folder",
32
+ "scopes": ["Mail.ReadWrite"],
33
+ "llmTip": "Creates a subfolder inside an existing mail folder. Use list-mail-folders or list-mail-child-folders to find the parent folder ID."
34
+ },
35
+ {
36
+ "pathPattern": "/me/mailFolders/{mailFolder-id}",
37
+ "method": "patch",
38
+ "toolName": "update-mail-folder",
39
+ "scopes": ["Mail.ReadWrite"],
40
+ "llmTip": "Renames a mail folder by updating its displayName. Use list-mail-folders to find the folder ID."
41
+ },
42
+ {
43
+ "pathPattern": "/me/mailFolders/{mailFolder-id}",
44
+ "method": "delete",
45
+ "toolName": "delete-mail-folder",
46
+ "scopes": ["Mail.ReadWrite"],
47
+ "llmTip": "Deletes a mail folder and all its contents. This action is irreversible. Use list-mail-folders to find the folder ID."
48
+ },
21
49
  {
22
50
  "pathPattern": "/me/mailFolders/{mailFolder-id}/messages",
23
51
  "method": "get",
@@ -245,7 +273,7 @@
245
273
  "scopes": ["Calendars.Read"],
246
274
  "supportsTimezone": true,
247
275
  "supportsExpandExtendedProperties": true,
248
- "llmTip": "Returns expanded recurring event instances (not just seriesMaster) within a date range for the default calendar. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Use get-specific-calendar-view if you need a non-default calendar."
276
+ "llmTip": "Returns expanded recurring event instances (not just seriesMaster) within a date range for the default calendar. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Use get-specific-calendar-view if you need a non-default calendar. To find Teams meetings, use $filter=isOnlineMeeting eq true. To search by subject, use $filter=contains(subject,'keyword'). Teams meetings include a joinWebUrl property needed for transcript access via list-online-meetings."
249
277
  },
250
278
  {
251
279
  "pathPattern": "/me/calendars/{calendar-id}/calendarView",
@@ -254,7 +282,7 @@
254
282
  "scopes": ["Calendars.Read"],
255
283
  "supportsTimezone": true,
256
284
  "supportsExpandExtendedProperties": true,
257
- "llmTip": "Returns expanded recurring event instances (not just seriesMaster) within a date range for a specific calendar. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Each instance includes seriesMasterId and type (occurrence/exception) fields for recurring event linkage. Use fetchAllPages=true to retrieve all results when there are many events."
285
+ "llmTip": "Returns expanded recurring event instances (not just seriesMaster) within a date range for a specific calendar. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Each instance includes seriesMasterId and type (occurrence/exception) fields for recurring event linkage. Use fetchAllPages=true to retrieve all results when there are many events. To find Teams meetings, use $filter=isOnlineMeeting eq true. Teams meetings include a joinWebUrl property needed for transcript access via list-online-meetings."
258
286
  },
259
287
  {
260
288
  "pathPattern": "/me/calendars/{calendar-id}/events/{event-id}/instances",
@@ -466,6 +494,12 @@
466
494
  "toolName": "update-planner-task",
467
495
  "scopes": ["Tasks.ReadWrite"]
468
496
  },
497
+ {
498
+ "pathPattern": "/planner/tasks/{plannerTask-id}/details",
499
+ "method": "get",
500
+ "toolName": "get-planner-task-details",
501
+ "scopes": ["Tasks.Read"]
502
+ },
469
503
  {
470
504
  "pathPattern": "/planner/tasks/{plannerTask-id}/details",
471
505
  "method": "patch",
@@ -693,6 +727,7 @@
693
727
  "pathPattern": "/me/onlineMeetings",
694
728
  "method": "get",
695
729
  "toolName": "list-online-meetings",
730
+ "scopes": ["OnlineMeetings.Read"],
696
731
  "workScopes": ["OnlineMeetings.Read"],
697
732
  "llmTip": "List online meetings. Use $filter=joinWebUrl eq '{url}' to find a specific meeting by its Teams join link. Returns meeting IDs needed for transcript access."
698
733
  },
@@ -700,6 +735,7 @@
700
735
  "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/transcripts",
701
736
  "method": "get",
702
737
  "toolName": "list-meeting-transcripts",
738
+ "scopes": ["OnlineMeetingTranscript.Read.All"],
703
739
  "workScopes": ["OnlineMeetingTranscript.Read.All"],
704
740
  "llmTip": "Lists available transcripts for a meeting. Get the meeting ID first via list-online-meetings."
705
741
  },
@@ -707,10 +743,26 @@
707
743
  "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/transcripts/{callTranscript-id}/content",
708
744
  "method": "get",
709
745
  "toolName": "get-meeting-transcript-content",
746
+ "scopes": ["OnlineMeetingTranscript.Read.All"],
710
747
  "workScopes": ["OnlineMeetingTranscript.Read.All"],
711
748
  "acceptType": "text/vtt",
712
749
  "llmTip": "Returns the transcript content in WebVTT format with speaker identification and timestamps."
713
750
  },
751
+ {
752
+ "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/recordings",
753
+ "method": "get",
754
+ "toolName": "list-meeting-recordings",
755
+ "workScopes": ["OnlineMeetingRecording.Read.All"],
756
+ "llmTip": "Lists recordings for a meeting. Each recording has createdDateTime, endDateTime, meetingOrganizer, contentCorrelationId (links to corresponding transcript). For recurring meetings, there is one recording per occurrence."
757
+ },
758
+ {
759
+ "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/recordings/{callRecording-id}/content",
760
+ "method": "get",
761
+ "toolName": "get-meeting-recording-content",
762
+ "workScopes": ["OnlineMeetingRecording.Read.All"],
763
+ "returnDownloadUrl": true,
764
+ "llmTip": "Returns a temporary download URL for the meeting recording in MP4 format. Use the download URL to access the video file. The recording may be large — do not attempt to stream it inline."
765
+ },
714
766
  {
715
767
  "pathPattern": "/groups/{group-id}/conversations",
716
768
  "method": "get",