@softeria/ms-365-mcp-server 0.46.2 → 0.47.1

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/dist/auth.js CHANGED
@@ -48,6 +48,30 @@ function ensureParentDir(filePath) {
48
48
  const dir = path.dirname(filePath);
49
49
  fs.mkdirSync(dir, { recursive: true, mode: 448 });
50
50
  }
51
+ function wrapCache(data) {
52
+ return JSON.stringify({ _cacheEnvelope: true, data, savedAt: Date.now() });
53
+ }
54
+ function unwrapCache(raw) {
55
+ try {
56
+ const parsed = JSON.parse(raw);
57
+ if (parsed._cacheEnvelope && typeof parsed.data === "string") {
58
+ return { data: parsed.data, savedAt: parsed.savedAt };
59
+ }
60
+ } catch {
61
+ }
62
+ return { data: raw };
63
+ }
64
+ function pickNewest(keytarRaw, fileRaw) {
65
+ if (!keytarRaw && !fileRaw) return void 0;
66
+ if (keytarRaw && !fileRaw) return unwrapCache(keytarRaw).data;
67
+ if (!keytarRaw && fileRaw) return unwrapCache(fileRaw).data;
68
+ const kt = unwrapCache(keytarRaw);
69
+ const file = unwrapCache(fileRaw);
70
+ if (kt.savedAt === void 0 && file.savedAt === void 0) return kt.data;
71
+ if (kt.savedAt !== void 0 && file.savedAt === void 0) return kt.data;
72
+ if (kt.savedAt === void 0 && file.savedAt !== void 0) return file.data;
73
+ return kt.savedAt >= file.savedAt ? kt.data : file.data;
74
+ }
51
75
  function createMsalConfig(secrets) {
52
76
  const cloudEndpoints = getCloudEndpoints(secrets.cloudType);
53
77
  return {
@@ -126,24 +150,21 @@ class AuthManager {
126
150
  }
127
151
  async loadTokenCache() {
128
152
  try {
129
- let cacheData;
153
+ let keytarRaw;
130
154
  try {
131
155
  const kt = await getKeytar();
132
156
  if (kt) {
133
- const cachedData = await kt.getPassword(SERVICE_NAME, TOKEN_CACHE_ACCOUNT);
134
- if (cachedData) {
135
- cacheData = cachedData;
136
- }
157
+ keytarRaw = await kt.getPassword(SERVICE_NAME, TOKEN_CACHE_ACCOUNT) ?? void 0;
137
158
  }
138
159
  } catch (keytarError) {
139
- logger.warn(
140
- `Keychain access failed, falling back to file storage: ${keytarError.message}`
141
- );
160
+ logger.warn(`Keychain access failed: ${keytarError.message}`);
142
161
  }
162
+ let fileRaw;
143
163
  const cachePath = getTokenCachePath();
144
- if (!cacheData && existsSync(cachePath)) {
145
- cacheData = readFileSync(cachePath, "utf8");
164
+ if (existsSync(cachePath)) {
165
+ fileRaw = readFileSync(cachePath, "utf8");
146
166
  }
167
+ const cacheData = pickNewest(keytarRaw, fileRaw);
147
168
  if (cacheData) {
148
169
  this.msalApp.getTokenCache().deserialize(cacheData);
149
170
  }
@@ -154,24 +175,23 @@ class AuthManager {
154
175
  }
155
176
  async loadSelectedAccount() {
156
177
  try {
157
- let selectedAccountData;
178
+ let keytarRaw;
158
179
  try {
159
180
  const kt = await getKeytar();
160
181
  if (kt) {
161
- const cachedData = await kt.getPassword(SERVICE_NAME, SELECTED_ACCOUNT_KEY);
162
- if (cachedData) {
163
- selectedAccountData = cachedData;
164
- }
182
+ keytarRaw = await kt.getPassword(SERVICE_NAME, SELECTED_ACCOUNT_KEY) ?? void 0;
165
183
  }
166
184
  } catch (keytarError) {
167
185
  logger.warn(
168
- `Keychain access failed for selected account, falling back to file storage: ${keytarError.message}`
186
+ `Keychain access failed for selected account: ${keytarError.message}`
169
187
  );
170
188
  }
189
+ let fileRaw;
171
190
  const accountPath = getSelectedAccountPath();
172
- if (!selectedAccountData && existsSync(accountPath)) {
173
- selectedAccountData = readFileSync(accountPath, "utf8");
191
+ if (existsSync(accountPath)) {
192
+ fileRaw = readFileSync(accountPath, "utf8");
174
193
  }
194
+ const selectedAccountData = pickNewest(keytarRaw, fileRaw);
175
195
  if (selectedAccountData) {
176
196
  const parsed = JSON.parse(selectedAccountData);
177
197
  this.selectedAccountId = parsed.accountId;
@@ -183,15 +203,15 @@ class AuthManager {
183
203
  }
184
204
  async saveTokenCache() {
185
205
  try {
186
- const cacheData = this.msalApp.getTokenCache().serialize();
206
+ const stamped = wrapCache(this.msalApp.getTokenCache().serialize());
187
207
  try {
188
208
  const kt = await getKeytar();
189
209
  if (kt) {
190
- await kt.setPassword(SERVICE_NAME, TOKEN_CACHE_ACCOUNT, cacheData);
210
+ await kt.setPassword(SERVICE_NAME, TOKEN_CACHE_ACCOUNT, stamped);
191
211
  } else {
192
212
  const cachePath = getTokenCachePath();
193
213
  ensureParentDir(cachePath);
194
- fs.writeFileSync(cachePath, cacheData, { mode: 384 });
214
+ fs.writeFileSync(cachePath, stamped, { mode: 384 });
195
215
  }
196
216
  } catch (keytarError) {
197
217
  logger.warn(
@@ -199,7 +219,7 @@ class AuthManager {
199
219
  );
200
220
  const cachePath = getTokenCachePath();
201
221
  ensureParentDir(cachePath);
202
- fs.writeFileSync(cachePath, cacheData, { mode: 384 });
222
+ fs.writeFileSync(cachePath, stamped, { mode: 384 });
203
223
  }
204
224
  } catch (error) {
205
225
  logger.error(`Error saving token cache: ${error.message}`);
@@ -207,15 +227,15 @@ class AuthManager {
207
227
  }
208
228
  async saveSelectedAccount() {
209
229
  try {
210
- const selectedAccountData = JSON.stringify({ accountId: this.selectedAccountId });
230
+ const stamped = wrapCache(JSON.stringify({ accountId: this.selectedAccountId }));
211
231
  try {
212
232
  const kt = await getKeytar();
213
233
  if (kt) {
214
- await kt.setPassword(SERVICE_NAME, SELECTED_ACCOUNT_KEY, selectedAccountData);
234
+ await kt.setPassword(SERVICE_NAME, SELECTED_ACCOUNT_KEY, stamped);
215
235
  } else {
216
236
  const accountPath = getSelectedAccountPath();
217
237
  ensureParentDir(accountPath);
218
- fs.writeFileSync(accountPath, selectedAccountData, { mode: 384 });
238
+ fs.writeFileSync(accountPath, stamped, { mode: 384 });
219
239
  }
220
240
  } catch (keytarError) {
221
241
  logger.warn(
@@ -223,7 +243,7 @@ class AuthManager {
223
243
  );
224
244
  const accountPath = getSelectedAccountPath();
225
245
  ensureParentDir(accountPath);
226
- fs.writeFileSync(accountPath, selectedAccountData, { mode: 384 });
246
+ fs.writeFileSync(accountPath, stamped, { mode: 384 });
227
247
  }
228
248
  } catch (error) {
229
249
  logger.error(`Error saving selected account: ${error.message}`);
@@ -524,5 +544,8 @@ export {
524
544
  buildScopesFromEndpoints,
525
545
  auth_default as default,
526
546
  getSelectedAccountPath,
527
- getTokenCachePath
547
+ getTokenCachePath,
548
+ pickNewest,
549
+ unwrapCache,
550
+ wrapCache
528
551
  };
@@ -658,6 +658,62 @@
658
658
  "toolName": "list-team-members",
659
659
  "workScopes": ["TeamMember.Read.All"]
660
660
  },
661
+ {
662
+ "pathPattern": "/teams/{team-id}/tags",
663
+ "method": "get",
664
+ "toolName": "list-team-tags",
665
+ "workScopes": ["TeamworkTag.Read.All"],
666
+ "llmTip": "List all tags in a team. Returns tag id, displayName, memberCount, and tagType (standard or scheduled). Use list-joined-teams to find the team ID first."
667
+ },
668
+ {
669
+ "pathPattern": "/teams/{team-id}/tags",
670
+ "method": "post",
671
+ "toolName": "create-team-tag",
672
+ "workScopes": ["TeamworkTag.ReadWrite.All"],
673
+ "llmTip": "Create a new tag in a team. Body requires displayName (max 40 chars) and an optional members array (max 25 at creation, each with a userId). Use list-team-members to find user IDs. Add more members individually with add-team-tag-member after creation."
674
+ },
675
+ {
676
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}",
677
+ "method": "get",
678
+ "toolName": "get-team-tag",
679
+ "workScopes": ["TeamworkTag.Read.All"],
680
+ "llmTip": "Get details of a specific tag including displayName, memberCount, and tagType. Use list-team-tags to find the tag ID."
681
+ },
682
+ {
683
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}",
684
+ "method": "patch",
685
+ "toolName": "update-team-tag",
686
+ "workScopes": ["TeamworkTag.ReadWrite.All"],
687
+ "llmTip": "Update a tag's displayName (max 40 chars). Use list-team-tags to find the tag ID."
688
+ },
689
+ {
690
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}",
691
+ "method": "delete",
692
+ "toolName": "delete-team-tag",
693
+ "workScopes": ["TeamworkTag.ReadWrite.All"],
694
+ "llmTip": "Permanently delete a tag from a team. This action is irreversible. Use list-team-tags to find the tag ID."
695
+ },
696
+ {
697
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}/members",
698
+ "method": "get",
699
+ "toolName": "list-team-tag-members",
700
+ "workScopes": ["TeamworkTag.Read.All"],
701
+ "llmTip": "List all members of a specific tag. Returns member id, displayName, tenantId, and userId. Use list-team-tags to find the tag ID."
702
+ },
703
+ {
704
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}/members",
705
+ "method": "post",
706
+ "toolName": "add-team-tag-member",
707
+ "workScopes": ["TeamworkTag.ReadWrite.All"],
708
+ "llmTip": "Add a member to an existing tag. Body requires userId. Use list-team-members to find the user ID. Max 200 members per tag. Note: this operation requires application permissions (delegated not supported)."
709
+ },
710
+ {
711
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}/members/{teamworkTagMember-id}",
712
+ "method": "delete",
713
+ "toolName": "remove-team-tag-member",
714
+ "workScopes": ["TeamworkTag.ReadWrite.All"],
715
+ "llmTip": "Remove a member from a tag. Use list-team-tag-members to find the member ID."
716
+ },
661
717
  {
662
718
  "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/replies",
663
719
  "method": "get",
@@ -792,6 +848,27 @@
792
848
  "returnDownloadUrl": true,
793
849
  "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."
794
850
  },
851
+ {
852
+ "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/attendanceReports",
853
+ "method": "get",
854
+ "toolName": "list-meeting-attendance-reports",
855
+ "workScopes": ["OnlineMeetingArtifact.Read.All"],
856
+ "llmTip": "Lists attendance reports for a meeting. Each report has meetingStartDateTime, meetingEndDateTime, totalParticipantCount. For recurring meetings, there is one report per occurrence. Get the meeting ID first via list-online-meetings."
857
+ },
858
+ {
859
+ "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/attendanceReports/{meetingAttendanceReport-id}",
860
+ "method": "get",
861
+ "toolName": "get-meeting-attendance-report",
862
+ "workScopes": ["OnlineMeetingArtifact.Read.All"],
863
+ "llmTip": "Gets a specific attendance report with totalParticipantCount, meetingStartDateTime, meetingEndDateTime."
864
+ },
865
+ {
866
+ "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/attendanceReports/{meetingAttendanceReport-id}/attendanceRecords",
867
+ "method": "get",
868
+ "toolName": "list-meeting-attendance-records",
869
+ "workScopes": ["OnlineMeetingArtifact.Read.All"],
870
+ "llmTip": "Lists individual attendance records for a meeting report. Each record has: identity (displayName), emailAddress, role (Organizer/Presenter/Attendee), totalAttendanceInSeconds, and attendanceIntervals with joinDateTime/leaveDateTime. Use to determine who attended, how long, and when they joined/left."
871
+ },
795
872
  {
796
873
  "pathPattern": "/groups/{group-id}/conversations",
797
874
  "method": "get",
@@ -2873,6 +2873,69 @@ 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_virtualEventExternalInformation = z.object({
2877
+ applicationId: z.string().describe("Identifier of the application that hosts the externalEventId. Read-only.").nullish(),
2878
+ externalEventId: z.string().describe(
2879
+ "The identifier for a virtualEventExternalInformation object that associates the virtual event with an event ID in an external application. This association bundles all the information (both supported and not supported in virtualEvent) into one virtual event object. Optional. If set, the maximum supported length is 256 characters."
2880
+ ).nullish()
2881
+ }).passthrough();
2882
+ const microsoft_graph_attendanceInterval = z.object({
2883
+ durationInSeconds: z.number().gte(-2147483648).lte(2147483647).describe(
2884
+ "Duration of the meeting interval in seconds; that is, the difference between joinDateTime and leaveDateTime."
2885
+ ).nullish(),
2886
+ joinDateTime: z.string().regex(
2887
+ /^[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])$/
2888
+ ).datetime({ offset: true }).describe("The time the attendee joined in UTC.").nullish(),
2889
+ leaveDateTime: z.string().regex(
2890
+ /^[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])$/
2891
+ ).datetime({ offset: true }).describe("The time the attendee left in UTC.").nullish()
2892
+ }).passthrough();
2893
+ const microsoft_graph_virtualEventExternalRegistrationInformation = z.object({
2894
+ referrer: z.string().describe(
2895
+ "A URL or string that represents the location from which the registrant registered. Optional."
2896
+ ).nullish(),
2897
+ registrationId: z.string().describe(
2898
+ "The identifier for a virtualEventExternalRegistrationInformation object. Optional. If set, the maximum supported length is 256 characters."
2899
+ ).nullish()
2900
+ }).passthrough();
2901
+ const microsoft_graph_attendanceRecord = z.object({
2902
+ id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
2903
+ attendanceIntervals: z.array(microsoft_graph_attendanceInterval).describe("List of time periods between joining and leaving a meeting.").optional(),
2904
+ emailAddress: z.string().describe("Email address of the user associated with this attendance record.").nullish(),
2905
+ externalRegistrationInformation: microsoft_graph_virtualEventExternalRegistrationInformation.optional(),
2906
+ identity: microsoft_graph_identity.optional(),
2907
+ registrationId: z.string().describe(
2908
+ "Unique identifier of a virtualEventRegistration that is available to all participants registered for the virtualEventWebinar."
2909
+ ).nullish(),
2910
+ role: z.string().describe(
2911
+ "Role of the attendee. The possible values are: None, Attendee, Presenter, and Organizer."
2912
+ ).nullish(),
2913
+ totalAttendanceInSeconds: z.number().gte(-2147483648).lte(2147483647).describe("Total duration of the attendances in seconds.").nullish()
2914
+ }).passthrough();
2915
+ const microsoft_graph_meetingAttendanceReport = z.object({
2916
+ id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
2917
+ externalEventInformation: z.array(microsoft_graph_virtualEventExternalInformation).describe(
2918
+ "The external information of a virtual event. Returned only for event organizers or coorganizers. Read-only."
2919
+ ).optional(),
2920
+ meetingEndDateTime: z.string().regex(
2921
+ /^[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])$/
2922
+ ).datetime({ offset: true }).describe("UTC time when the meeting ended. Read-only.").nullish(),
2923
+ meetingStartDateTime: z.string().regex(
2924
+ /^[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])$/
2925
+ ).datetime({ offset: true }).describe("UTC time when the meeting started. Read-only.").nullish(),
2926
+ totalParticipantCount: z.number().gte(-2147483648).lte(2147483647).describe("Total number of participants. Read-only.").nullish(),
2927
+ attendanceRecords: z.array(microsoft_graph_attendanceRecord).describe("List of attendance records of an attendance report. Read-only.").optional()
2928
+ }).passthrough();
2929
+ const microsoft_graph_meetingAttendanceReportCollectionResponse = z.object({
2930
+ "@odata.count": z.number().int().nullable(),
2931
+ "@odata.nextLink": z.string().nullable(),
2932
+ value: z.array(microsoft_graph_meetingAttendanceReport)
2933
+ }).partial().passthrough();
2934
+ const microsoft_graph_attendanceRecordCollectionResponse = z.object({
2935
+ "@odata.count": z.number().int().nullable(),
2936
+ "@odata.nextLink": z.string().nullable(),
2937
+ value: z.array(microsoft_graph_attendanceRecord)
2938
+ }).partial().passthrough();
2876
2939
  const microsoft_graph_callRecording = z.object({
2877
2940
  id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
2878
2941
  callId: z.string().describe("The unique identifier for the call that is related to this recording. Read-only.").nullish(),
@@ -3496,6 +3559,34 @@ const microsoft_graph_conversationMemberCollectionResponse = z.object({
3496
3559
  "@odata.nextLink": z.string().nullable(),
3497
3560
  value: z.array(microsoft_graph_conversationMember)
3498
3561
  }).partial().passthrough();
3562
+ const microsoft_graph_teamworkTagType = z.enum(["standard", "unknownFutureValue"]);
3563
+ const microsoft_graph_teamworkTagMember = z.object({
3564
+ id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
3565
+ displayName: z.string().describe("The member's display name.").nullish(),
3566
+ tenantId: z.string().describe("The ID of the tenant that the tag member is a part of.").nullish(),
3567
+ userId: z.string().describe("The user ID of the member.").nullish()
3568
+ }).passthrough();
3569
+ const microsoft_graph_teamworkTag = z.object({
3570
+ id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
3571
+ description: z.string().describe(
3572
+ "The description of the tag as it appears to the user in Microsoft Teams. A teamworkTag can't have more than 200 teamworkTagMembers."
3573
+ ).nullish(),
3574
+ displayName: z.string().describe("The name of the tag as it appears to the user in Microsoft Teams.").nullish(),
3575
+ memberCount: z.number().gte(-2147483648).lte(2147483647).describe("The number of users assigned to the tag.").nullish(),
3576
+ tagType: microsoft_graph_teamworkTagType.optional(),
3577
+ teamId: z.string().describe("ID of the team in which the tag is defined.").nullish(),
3578
+ members: z.array(microsoft_graph_teamworkTagMember).describe("Users assigned to the tag.").optional()
3579
+ }).passthrough();
3580
+ const microsoft_graph_teamworkTagCollectionResponse = z.object({
3581
+ "@odata.count": z.number().int().nullable(),
3582
+ "@odata.nextLink": z.string().nullable(),
3583
+ value: z.array(microsoft_graph_teamworkTag)
3584
+ }).partial().passthrough();
3585
+ const microsoft_graph_teamworkTagMemberCollectionResponse = z.object({
3586
+ "@odata.count": z.number().int().nullable(),
3587
+ "@odata.nextLink": z.string().nullable(),
3588
+ value: z.array(microsoft_graph_teamworkTagMember)
3589
+ }).partial().passthrough();
3499
3590
  const microsoft_graph_userCollectionResponse = z.object({
3500
3591
  "@odata.count": z.number().int().nullable(),
3501
3592
  "@odata.nextLink": z.string().nullable(),
@@ -3778,6 +3869,13 @@ const schemas = {
3778
3869
  microsoft_graph_joinMeetingIdSettings,
3779
3870
  microsoft_graph_onlineMeeting,
3780
3871
  microsoft_graph_onlineMeetingCollectionResponse,
3872
+ microsoft_graph_virtualEventExternalInformation,
3873
+ microsoft_graph_attendanceInterval,
3874
+ microsoft_graph_virtualEventExternalRegistrationInformation,
3875
+ microsoft_graph_attendanceRecord,
3876
+ microsoft_graph_meetingAttendanceReport,
3877
+ microsoft_graph_meetingAttendanceReportCollectionResponse,
3878
+ microsoft_graph_attendanceRecordCollectionResponse,
3781
3879
  microsoft_graph_callRecording,
3782
3880
  microsoft_graph_callRecordingCollectionResponse,
3783
3881
  microsoft_graph_callTranscript,
@@ -3849,6 +3947,11 @@ const schemas = {
3849
3947
  BaseDeltaFunctionResponse,
3850
3948
  microsoft_graph_channelCollectionResponse,
3851
3949
  microsoft_graph_conversationMemberCollectionResponse,
3950
+ microsoft_graph_teamworkTagType,
3951
+ microsoft_graph_teamworkTagMember,
3952
+ microsoft_graph_teamworkTag,
3953
+ microsoft_graph_teamworkTagCollectionResponse,
3954
+ microsoft_graph_teamworkTagMemberCollectionResponse,
3852
3955
  microsoft_graph_userCollectionResponse
3853
3956
  };
3854
3957
  const endpoints = makeApi([
@@ -6377,6 +6480,126 @@ resource.`,
6377
6480
  ],
6378
6481
  response: z.void()
6379
6482
  },
6483
+ {
6484
+ method: "get",
6485
+ path: "/me/onlineMeetings/:onlineMeetingId/attendanceReports",
6486
+ alias: "list-meeting-attendance-reports",
6487
+ description: `Get a list of meetingAttendanceReport objects for an onlineMeeting or a virtualEvent. Each time an online meeting or a virtual event ends, an attendance report is generated for that session.`,
6488
+ requestFormat: "json",
6489
+ parameters: [
6490
+ {
6491
+ name: "$top",
6492
+ type: "Query",
6493
+ schema: z.number().int().gte(0).describe("Show only the first n items").optional()
6494
+ },
6495
+ {
6496
+ name: "$skip",
6497
+ type: "Query",
6498
+ schema: z.number().int().gte(0).describe("Skip the first n items").optional()
6499
+ },
6500
+ {
6501
+ name: "$search",
6502
+ type: "Query",
6503
+ schema: z.string().describe("Search items by search phrases").optional()
6504
+ },
6505
+ {
6506
+ name: "$filter",
6507
+ type: "Query",
6508
+ schema: z.string().describe("Filter items by property values").optional()
6509
+ },
6510
+ {
6511
+ name: "$count",
6512
+ type: "Query",
6513
+ schema: z.boolean().describe("Include count of items").optional()
6514
+ },
6515
+ {
6516
+ name: "$orderby",
6517
+ type: "Query",
6518
+ schema: z.array(z.string()).describe("Order items by property values").optional()
6519
+ },
6520
+ {
6521
+ name: "$select",
6522
+ type: "Query",
6523
+ schema: z.array(z.string()).describe("Select properties to be returned").optional()
6524
+ },
6525
+ {
6526
+ name: "$expand",
6527
+ type: "Query",
6528
+ schema: z.array(z.string()).describe("Expand related entities").optional()
6529
+ }
6530
+ ],
6531
+ response: z.void()
6532
+ },
6533
+ {
6534
+ method: "get",
6535
+ path: "/me/onlineMeetings/:onlineMeetingId/attendanceReports/:meetingAttendanceReportId",
6536
+ alias: "get-meeting-attendance-report",
6537
+ description: `Get the meetingAttendanceReport for an onlineMeeting or a virtualEvent. When an online meeting ends, an attendance report is generated for that session.`,
6538
+ requestFormat: "json",
6539
+ parameters: [
6540
+ {
6541
+ name: "$select",
6542
+ type: "Query",
6543
+ schema: z.array(z.string()).describe("Select properties to be returned").optional()
6544
+ },
6545
+ {
6546
+ name: "$expand",
6547
+ type: "Query",
6548
+ schema: z.array(z.string()).describe("Expand related entities").optional()
6549
+ }
6550
+ ],
6551
+ response: z.void()
6552
+ },
6553
+ {
6554
+ method: "get",
6555
+ path: "/me/onlineMeetings/:onlineMeetingId/attendanceReports/:meetingAttendanceReportId/attendanceRecords",
6556
+ alias: "list-meeting-attendance-records",
6557
+ description: `Get a list of attendanceRecord objects and their properties.`,
6558
+ requestFormat: "json",
6559
+ parameters: [
6560
+ {
6561
+ name: "$top",
6562
+ type: "Query",
6563
+ schema: z.number().int().gte(0).describe("Show only the first n items").optional()
6564
+ },
6565
+ {
6566
+ name: "$skip",
6567
+ type: "Query",
6568
+ schema: z.number().int().gte(0).describe("Skip the first n items").optional()
6569
+ },
6570
+ {
6571
+ name: "$search",
6572
+ type: "Query",
6573
+ schema: z.string().describe("Search items by search phrases").optional()
6574
+ },
6575
+ {
6576
+ name: "$filter",
6577
+ type: "Query",
6578
+ schema: z.string().describe("Filter items by property values").optional()
6579
+ },
6580
+ {
6581
+ name: "$count",
6582
+ type: "Query",
6583
+ schema: z.boolean().describe("Include count of items").optional()
6584
+ },
6585
+ {
6586
+ name: "$orderby",
6587
+ type: "Query",
6588
+ schema: z.array(z.string()).describe("Order items by property values").optional()
6589
+ },
6590
+ {
6591
+ name: "$select",
6592
+ type: "Query",
6593
+ schema: z.array(z.string()).describe("Select properties to be returned").optional()
6594
+ },
6595
+ {
6596
+ name: "$expand",
6597
+ type: "Query",
6598
+ schema: z.array(z.string()).describe("Expand related entities").optional()
6599
+ }
6600
+ ],
6601
+ response: z.void()
6602
+ },
6380
6603
  {
6381
6604
  method: "get",
6382
6605
  path: "/me/onlineMeetings/:onlineMeetingId/recordings",
@@ -7734,6 +7957,204 @@ To monitor future changes, call the delta API by using the @odata.deltaLink in t
7734
7957
  ],
7735
7958
  response: z.void()
7736
7959
  },
7960
+ {
7961
+ method: "get",
7962
+ path: "/teams/:teamId/tags",
7963
+ alias: "list-team-tags",
7964
+ description: `Get a list of the tag objects and their properties.`,
7965
+ requestFormat: "json",
7966
+ parameters: [
7967
+ {
7968
+ name: "$top",
7969
+ type: "Query",
7970
+ schema: z.number().int().gte(0).describe("Show only the first n items").optional()
7971
+ },
7972
+ {
7973
+ name: "$skip",
7974
+ type: "Query",
7975
+ schema: z.number().int().gte(0).describe("Skip the first n items").optional()
7976
+ },
7977
+ {
7978
+ name: "$search",
7979
+ type: "Query",
7980
+ schema: z.string().describe("Search items by search phrases").optional()
7981
+ },
7982
+ {
7983
+ name: "$filter",
7984
+ type: "Query",
7985
+ schema: z.string().describe("Filter items by property values").optional()
7986
+ },
7987
+ {
7988
+ name: "$count",
7989
+ type: "Query",
7990
+ schema: z.boolean().describe("Include count of items").optional()
7991
+ },
7992
+ {
7993
+ name: "$orderby",
7994
+ type: "Query",
7995
+ schema: z.array(z.string()).describe("Order items by property values").optional()
7996
+ },
7997
+ {
7998
+ name: "$select",
7999
+ type: "Query",
8000
+ schema: z.array(z.string()).describe("Select properties to be returned").optional()
8001
+ },
8002
+ {
8003
+ name: "$expand",
8004
+ type: "Query",
8005
+ schema: z.array(z.string()).describe("Expand related entities").optional()
8006
+ }
8007
+ ],
8008
+ response: z.void()
8009
+ },
8010
+ {
8011
+ method: "post",
8012
+ path: "/teams/:teamId/tags",
8013
+ alias: "create-team-tag",
8014
+ description: `Create a standard tag for members in a team.`,
8015
+ requestFormat: "json",
8016
+ parameters: [
8017
+ {
8018
+ name: "body",
8019
+ description: `New navigation property`,
8020
+ type: "Body",
8021
+ schema: microsoft_graph_teamworkTag
8022
+ }
8023
+ ],
8024
+ response: z.void()
8025
+ },
8026
+ {
8027
+ method: "get",
8028
+ path: "/teams/:teamId/tags/:teamworkTagId",
8029
+ alias: "get-team-tag",
8030
+ description: `Read the properties and relationships of a tag object.`,
8031
+ requestFormat: "json",
8032
+ parameters: [
8033
+ {
8034
+ name: "$select",
8035
+ type: "Query",
8036
+ schema: z.array(z.string()).describe("Select properties to be returned").optional()
8037
+ },
8038
+ {
8039
+ name: "$expand",
8040
+ type: "Query",
8041
+ schema: z.array(z.string()).describe("Expand related entities").optional()
8042
+ }
8043
+ ],
8044
+ response: z.void()
8045
+ },
8046
+ {
8047
+ method: "patch",
8048
+ path: "/teams/:teamId/tags/:teamworkTagId",
8049
+ alias: "update-team-tag",
8050
+ description: `Update the properties of a tag object.`,
8051
+ requestFormat: "json",
8052
+ parameters: [
8053
+ {
8054
+ name: "body",
8055
+ description: `New navigation property values`,
8056
+ type: "Body",
8057
+ schema: microsoft_graph_teamworkTag
8058
+ }
8059
+ ],
8060
+ response: z.void()
8061
+ },
8062
+ {
8063
+ method: "delete",
8064
+ path: "/teams/:teamId/tags/:teamworkTagId",
8065
+ alias: "delete-team-tag",
8066
+ description: `Delete a tag object permanently.`,
8067
+ requestFormat: "json",
8068
+ parameters: [
8069
+ {
8070
+ name: "If-Match",
8071
+ type: "Header",
8072
+ schema: z.string().describe("ETag").optional()
8073
+ }
8074
+ ],
8075
+ response: z.void()
8076
+ },
8077
+ {
8078
+ method: "get",
8079
+ path: "/teams/:teamId/tags/:teamworkTagId/members",
8080
+ alias: "list-team-tag-members",
8081
+ description: `Get a list of the members of a standard tag in a team and their properties.`,
8082
+ requestFormat: "json",
8083
+ parameters: [
8084
+ {
8085
+ name: "$top",
8086
+ type: "Query",
8087
+ schema: z.number().int().gte(0).describe("Show only the first n items").optional()
8088
+ },
8089
+ {
8090
+ name: "$skip",
8091
+ type: "Query",
8092
+ schema: z.number().int().gte(0).describe("Skip the first n items").optional()
8093
+ },
8094
+ {
8095
+ name: "$search",
8096
+ type: "Query",
8097
+ schema: z.string().describe("Search items by search phrases").optional()
8098
+ },
8099
+ {
8100
+ name: "$filter",
8101
+ type: "Query",
8102
+ schema: z.string().describe("Filter items by property values").optional()
8103
+ },
8104
+ {
8105
+ name: "$count",
8106
+ type: "Query",
8107
+ schema: z.boolean().describe("Include count of items").optional()
8108
+ },
8109
+ {
8110
+ name: "$orderby",
8111
+ type: "Query",
8112
+ schema: z.array(z.string()).describe("Order items by property values").optional()
8113
+ },
8114
+ {
8115
+ name: "$select",
8116
+ type: "Query",
8117
+ schema: z.array(z.string()).describe("Select properties to be returned").optional()
8118
+ },
8119
+ {
8120
+ name: "$expand",
8121
+ type: "Query",
8122
+ schema: z.array(z.string()).describe("Expand related entities").optional()
8123
+ }
8124
+ ],
8125
+ response: z.void()
8126
+ },
8127
+ {
8128
+ method: "post",
8129
+ path: "/teams/:teamId/tags/:teamworkTagId/members",
8130
+ alias: "add-team-tag-member",
8131
+ description: `Create a new teamworkTagMember object in a team.`,
8132
+ requestFormat: "json",
8133
+ parameters: [
8134
+ {
8135
+ name: "body",
8136
+ description: `New navigation property`,
8137
+ type: "Body",
8138
+ schema: microsoft_graph_teamworkTagMember
8139
+ }
8140
+ ],
8141
+ response: z.void()
8142
+ },
8143
+ {
8144
+ method: "delete",
8145
+ path: "/teams/:teamId/tags/:teamworkTagId/members/:teamworkTagMemberId",
8146
+ alias: "remove-team-tag-member",
8147
+ description: `Delete a member from a standard tag in a team.`,
8148
+ requestFormat: "json",
8149
+ parameters: [
8150
+ {
8151
+ name: "If-Match",
8152
+ type: "Header",
8153
+ schema: z.string().describe("ETag").optional()
8154
+ }
8155
+ ],
8156
+ response: z.void()
8157
+ },
7737
8158
  {
7738
8159
  method: "get",
7739
8160
  path: "/users",
@@ -1,10 +1,10 @@
1
- 2026-03-25 11:58:50 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
2
- 2026-03-25 11:58:50 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
3
- 2026-03-25 11:58:50 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
4
- 2026-03-25 11:58:50 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/messages
5
- 2026-03-25 11:58:50 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/calendar
6
- 2026-03-25 11:58:51 INFO: Using environment variables for secrets
7
- 2026-03-25 11:58:51 INFO: Using environment variables for secrets
8
- 2026-03-25 11:58:51 INFO: Using environment variables for secrets
9
- 2026-03-25 11:58:51 INFO: Using environment variables for secrets
10
- 2026-03-25 11:58:51 INFO: Using environment variables for secrets
1
+ 2026-03-25 13:58:07 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
2
+ 2026-03-25 13:58:07 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
3
+ 2026-03-25 13:58:07 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
4
+ 2026-03-25 13:58:07 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/messages
5
+ 2026-03-25 13:58:07 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/calendar
6
+ 2026-03-25 13:58:09 INFO: Using environment variables for secrets
7
+ 2026-03-25 13:58:09 INFO: Using environment variables for secrets
8
+ 2026-03-25 13:58:09 INFO: Using environment variables for secrets
9
+ 2026-03-25 13:58:09 INFO: Using environment variables for secrets
10
+ 2026-03-25 13:58:09 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.46.2",
3
+ "version": "0.47.1",
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",
@@ -658,6 +658,62 @@
658
658
  "toolName": "list-team-members",
659
659
  "workScopes": ["TeamMember.Read.All"]
660
660
  },
661
+ {
662
+ "pathPattern": "/teams/{team-id}/tags",
663
+ "method": "get",
664
+ "toolName": "list-team-tags",
665
+ "workScopes": ["TeamworkTag.Read.All"],
666
+ "llmTip": "List all tags in a team. Returns tag id, displayName, memberCount, and tagType (standard or scheduled). Use list-joined-teams to find the team ID first."
667
+ },
668
+ {
669
+ "pathPattern": "/teams/{team-id}/tags",
670
+ "method": "post",
671
+ "toolName": "create-team-tag",
672
+ "workScopes": ["TeamworkTag.ReadWrite.All"],
673
+ "llmTip": "Create a new tag in a team. Body requires displayName (max 40 chars) and an optional members array (max 25 at creation, each with a userId). Use list-team-members to find user IDs. Add more members individually with add-team-tag-member after creation."
674
+ },
675
+ {
676
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}",
677
+ "method": "get",
678
+ "toolName": "get-team-tag",
679
+ "workScopes": ["TeamworkTag.Read.All"],
680
+ "llmTip": "Get details of a specific tag including displayName, memberCount, and tagType. Use list-team-tags to find the tag ID."
681
+ },
682
+ {
683
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}",
684
+ "method": "patch",
685
+ "toolName": "update-team-tag",
686
+ "workScopes": ["TeamworkTag.ReadWrite.All"],
687
+ "llmTip": "Update a tag's displayName (max 40 chars). Use list-team-tags to find the tag ID."
688
+ },
689
+ {
690
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}",
691
+ "method": "delete",
692
+ "toolName": "delete-team-tag",
693
+ "workScopes": ["TeamworkTag.ReadWrite.All"],
694
+ "llmTip": "Permanently delete a tag from a team. This action is irreversible. Use list-team-tags to find the tag ID."
695
+ },
696
+ {
697
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}/members",
698
+ "method": "get",
699
+ "toolName": "list-team-tag-members",
700
+ "workScopes": ["TeamworkTag.Read.All"],
701
+ "llmTip": "List all members of a specific tag. Returns member id, displayName, tenantId, and userId. Use list-team-tags to find the tag ID."
702
+ },
703
+ {
704
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}/members",
705
+ "method": "post",
706
+ "toolName": "add-team-tag-member",
707
+ "workScopes": ["TeamworkTag.ReadWrite.All"],
708
+ "llmTip": "Add a member to an existing tag. Body requires userId. Use list-team-members to find the user ID. Max 200 members per tag. Note: this operation requires application permissions (delegated not supported)."
709
+ },
710
+ {
711
+ "pathPattern": "/teams/{team-id}/tags/{teamworkTag-id}/members/{teamworkTagMember-id}",
712
+ "method": "delete",
713
+ "toolName": "remove-team-tag-member",
714
+ "workScopes": ["TeamworkTag.ReadWrite.All"],
715
+ "llmTip": "Remove a member from a tag. Use list-team-tag-members to find the member ID."
716
+ },
661
717
  {
662
718
  "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/replies",
663
719
  "method": "get",
@@ -792,6 +848,27 @@
792
848
  "returnDownloadUrl": true,
793
849
  "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."
794
850
  },
851
+ {
852
+ "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/attendanceReports",
853
+ "method": "get",
854
+ "toolName": "list-meeting-attendance-reports",
855
+ "workScopes": ["OnlineMeetingArtifact.Read.All"],
856
+ "llmTip": "Lists attendance reports for a meeting. Each report has meetingStartDateTime, meetingEndDateTime, totalParticipantCount. For recurring meetings, there is one report per occurrence. Get the meeting ID first via list-online-meetings."
857
+ },
858
+ {
859
+ "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/attendanceReports/{meetingAttendanceReport-id}",
860
+ "method": "get",
861
+ "toolName": "get-meeting-attendance-report",
862
+ "workScopes": ["OnlineMeetingArtifact.Read.All"],
863
+ "llmTip": "Gets a specific attendance report with totalParticipantCount, meetingStartDateTime, meetingEndDateTime."
864
+ },
865
+ {
866
+ "pathPattern": "/me/onlineMeetings/{onlineMeeting-id}/attendanceReports/{meetingAttendanceReport-id}/attendanceRecords",
867
+ "method": "get",
868
+ "toolName": "list-meeting-attendance-records",
869
+ "workScopes": ["OnlineMeetingArtifact.Read.All"],
870
+ "llmTip": "Lists individual attendance records for a meeting report. Each record has: identity (displayName), emailAddress, role (Organizer/Presenter/Attendee), totalAttendanceInSeconds, and attendanceIntervals with joinDateTime/leaveDateTime. Use to determine who attended, how long, and when they joined/left."
871
+ },
795
872
  {
796
873
  "pathPattern": "/groups/{group-id}/conversations",
797
874
  "method": "get",