@softeria/ms-365-mcp-server 0.170.0 → 0.170.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.
@@ -1167,7 +1167,9 @@ describe("graph-tools", () => {
1167
1167
  "rejects mixed-case top values before Graph dispatch for %s",
1168
1168
  async (alias) => {
1169
1169
  mockEndpoints.push(makeEndpoint({ alias }));
1170
- mockEndpointsJson = [makeConfig({ toolName: alias })];
1170
+ mockEndpointsJson = [
1171
+ makeConfig({ toolName: alias, queryParams: ["startDateTime", "skiptoken"] })
1172
+ ];
1171
1173
  const graphClient = createMockGraphClient();
1172
1174
  const server = createMockServer();
1173
1175
  const { registerGraphTools } = await loadModule();
@@ -1183,7 +1185,7 @@ describe("graph-tools", () => {
1183
1185
  "rejects unsupported query options before Graph dispatch for %s",
1184
1186
  async (alias) => {
1185
1187
  mockEndpoints.push(makeEndpoint({ alias }));
1186
- mockEndpointsJson = [makeConfig({ toolName: alias })];
1188
+ mockEndpointsJson = [makeConfig({ toolName: alias, queryParams: [] })];
1187
1189
  const graphClient = createMockGraphClient();
1188
1190
  const server = createMockServer();
1189
1191
  const { registerGraphTools } = await loadModule();
@@ -1198,7 +1200,7 @@ describe("graph-tools", () => {
1198
1200
  );
1199
1201
  it("rejects oversized chat pages before Graph dispatch, including dollar-prefixed input", async () => {
1200
1202
  mockEndpoints.push(makeEndpoint({ alias: "list-chats" }));
1201
- mockEndpointsJson = [makeConfig({ toolName: "list-chats" })];
1203
+ mockEndpointsJson = [makeConfig({ toolName: "list-chats", maxTop: 50 })];
1202
1204
  const graphClient = createMockGraphClient();
1203
1205
  const server = createMockServer();
1204
1206
  const { registerGraphTools } = await loadModule();
@@ -1274,7 +1276,8 @@ describe("graph-tools", () => {
1274
1276
  endpoint,
1275
1277
  makeConfig({
1276
1278
  toolName: "list-calendar-events-delta",
1277
- pathPattern: "/me/calendarView/delta"
1279
+ pathPattern: "/me/calendarView/delta",
1280
+ queryParams: ["startDateTime", "skiptoken"]
1278
1281
  })
1279
1282
  );
1280
1283
  const schema = server.tools.get("list-calendar-events-delta").schema;
@@ -4291,7 +4294,7 @@ describe("graph-tools", () => {
4291
4294
  ["reply-to-chat-message", "post"],
4292
4295
  ["update-chat-message", "patch"]
4293
4296
  ];
4294
- async function call(alias, method, body) {
4297
+ async function call(alias, method, body, flagged = true) {
4295
4298
  mockEndpoints.push(
4296
4299
  makeEndpoint({
4297
4300
  alias,
@@ -4301,7 +4304,12 @@ describe("graph-tools", () => {
4301
4304
  })
4302
4305
  );
4303
4306
  mockEndpointsJson = [
4304
- makeConfig({ toolName: alias, method, pathPattern: "/chats/{chat-id}/messages" })
4307
+ makeConfig({
4308
+ toolName: alias,
4309
+ method,
4310
+ pathPattern: "/chats/{chat-id}/messages",
4311
+ mentionShorthand: flagged
4312
+ })
4305
4313
  ];
4306
4314
  const graphClient = createMockGraphClient([
4307
4315
  { content: [{ type: "text", text: JSON.stringify({ id: "m1" }) }] }
@@ -4335,10 +4343,13 @@ describe("graph-tools", () => {
4335
4343
  expect(result.content[0].text).toContain("not an Entra object id");
4336
4344
  expect(graphClient.graphRequest).not.toHaveBeenCalled();
4337
4345
  });
4338
- it("does not rewrite on a tool outside the seven", async () => {
4339
- const { graphClient } = await call("create-draft-email", "post", {
4340
- body: { contentType: "html", content: `<at user-id="${U1}">Jane</at>` }
4341
- });
4346
+ it("does not rewrite on a tool without mentionShorthand", async () => {
4347
+ const { graphClient } = await call(
4348
+ "create-draft-email",
4349
+ "post",
4350
+ { body: { contentType: "html", content: `<at user-id="${U1}">Jane</at>` } },
4351
+ false
4352
+ );
4342
4353
  expect(sent(graphClient).body.content).toBe(`<at user-id="${U1}">Jane</at>`);
4343
4354
  });
4344
4355
  it("lists only tools that exist in endpoints.json and documents the shorthand", async () => {
@@ -4410,7 +4421,7 @@ describe("graph-tools", () => {
4410
4421
  const ids = (result) => JSON.parse(result.content[0].text).value.map((m) => m.id);
4411
4422
  async function run(args, responses = [page(membersBody)], alias = "list-team-members", path = "/teams/{team-id}/members", outputFormat = "json") {
4412
4423
  mockEndpoints.push(makeEndpoint({ alias, path }));
4413
- mockEndpointsJson = [makeConfig({ toolName: alias, pathPattern: path })];
4424
+ mockEndpointsJson = [makeConfig({ toolName: alias, pathPattern: path, nameFilter: true })];
4414
4425
  const graphClient = createMockGraphClient(responses, outputFormat);
4415
4426
  const server = createMockServer();
4416
4427
  const { registerGraphTools } = await loadModule();
@@ -1330,7 +1330,8 @@
1330
1330
  "toolName": "list-todo-task-lists",
1331
1331
  "presets": ["personal", "tasks"],
1332
1332
  "scopes": ["Tasks.Read"],
1333
- "llmTip": "Lists all To Do task lists. Returns todoTaskList-id needed for all task operations. The default list is typically called 'Tasks'. NOTE: $select is NOT supported by this endpoint — do not pass select parameter, Graph returns 400."
1333
+ "queryParams": ["top", "skip", "filter", "skiptoken"],
1334
+ "llmTip": "Lists all To Do task lists. Returns todoTaskList-id needed for all task operations. The default list is typically called 'Tasks'."
1334
1335
  },
1335
1336
  {
1336
1337
  "pathPattern": "/me/todo/lists/{todoTaskList-id}/tasks",
@@ -1338,7 +1339,8 @@
1338
1339
  "toolName": "list-todo-tasks",
1339
1340
  "presets": ["personal", "tasks"],
1340
1341
  "scopes": ["Tasks.Read"],
1341
- "llmTip": "Lists tasks in a To Do list. Requires todoTaskList-id — use list-todo-task-lists to find it. NOTE: $select is NOT supported — do not pass select, Graph returns 400. Use $filter=status eq 'notStarted' or $filter=status eq 'completed' to filter by status. Use $top to limit results. Status values: 'notStarted', 'inProgress', 'completed', 'waitingOnOthers', 'deferred'."
1342
+ "queryParams": ["top", "skip", "filter", "orderby", "expand", "skiptoken"],
1343
+ "llmTip": "Lists tasks in a To Do list. Requires todoTaskList-id — use list-todo-task-lists to find it. Use $filter=status eq 'notStarted' or $filter=status eq 'completed' to filter by status. Use $top to limit results. Status values: 'notStarted', 'inProgress', 'completed', 'waitingOnOthers', 'deferred'. $orderby works on createdDateTime, lastModifiedDateTime, importance, status and reminderDateTime/dateTime, not on title or isReminderOn. A $filter on isReminderOn can also return 400; filter it client-side."
1342
1344
  },
1343
1345
  {
1344
1346
  "pathPattern": "/me/todo/lists/{todoTaskList-id}/tasks/{todoTask-id}",
@@ -1346,7 +1348,8 @@
1346
1348
  "toolName": "get-todo-task",
1347
1349
  "presets": ["personal", "tasks"],
1348
1350
  "scopes": ["Tasks.Read"],
1349
- "llmTip": "Returns a single To Do task. NOTE: $select is NOT supported — do not pass select parameter, Graph returns RequestBroker--ParseUri (400). Use $expand=linkedResources to include linked email/resource. Returns body content (HTML format), checklist items, and linked resources."
1351
+ "queryParams": ["expand"],
1352
+ "llmTip": "Returns a single To Do task. Use $expand=linkedResources to include linked email/resource. Returns body content (HTML format), checklist items, and linked resources."
1350
1353
  },
1351
1354
  {
1352
1355
  "pathPattern": "/me/todo/lists/{todoTaskList-id}/tasks",
@@ -1377,6 +1380,7 @@
1377
1380
  "toolName": "list-todo-linked-resources",
1378
1381
  "presets": ["personal", "tasks"],
1379
1382
  "scopes": ["Tasks.Read"],
1383
+ "queryParams": ["top", "skip", "filter", "orderby", "skiptoken"],
1380
1384
  "llmTip": "Lists resources linked to a To Do task (emails, URLs, etc.). Each linked resource has displayName, webUrl, applicationName, and externalId."
1381
1385
  },
1382
1386
  {
@@ -1401,7 +1405,8 @@
1401
1405
  "toolName": "list-todo-checklist-items",
1402
1406
  "presets": ["personal", "tasks"],
1403
1407
  "scopes": ["Tasks.Read"],
1404
- "llmTip": "Lists the steps (checklist items) of a To Do task. Each item has id, displayName, isChecked, checkedDateTime, and createdDateTime. Graph supports no OData query parameters on this endpoint, so do not pass select, filter, orderby, or top. Requires todoTaskListId from list-todo-task-lists and todoTaskId from list-todo-tasks."
1408
+ "queryParams": [],
1409
+ "llmTip": "Lists the steps (checklist items) of a To Do task. Each item has id, displayName, isChecked, checkedDateTime, and createdDateTime. Requires todoTaskListId from list-todo-task-lists and todoTaskId from list-todo-tasks."
1405
1410
  },
1406
1411
  {
1407
1412
  "pathPattern": "/me/todo/lists/{todoTaskList-id}/tasks/{todoTask-id}/checklistItems",
@@ -1722,6 +1727,7 @@
1722
1727
  "toolName": "list-chats",
1723
1728
  "presets": ["teams", "teams-write", "work"],
1724
1729
  "workScopes": ["Chat.ReadBasic"],
1730
+ "maxTop": 50,
1725
1731
  "llmTip": "Lists the signed-in user's chats. Only $filter, $top (max 50), $expand (members, lastMessagePreview) and $orderby are supported; $orderby accepts just lastMessagePreview/createdDateTime in descending order. Other options are rejected: $search returns 400 'Query option Search is not allowed', and ordering by lastUpdatedDateTime returns 400 BadRequest. Meeting chats have chatType=meeting and carry the meeting subject in topic."
1726
1732
  },
1727
1733
  {
@@ -1737,6 +1743,7 @@
1737
1743
  "toolName": "list-chat-members",
1738
1744
  "presets": ["teams", "work"],
1739
1745
  "workScopes": ["ChatMember.Read"],
1746
+ "nameFilter": true,
1740
1747
  "llmTip": "Lists members of a chat. Each member has displayName, email, roles (owner/guest), and visibleHistoryStartDateTime. For meeting chats, use the chatInfo.threadId from get-online-meeting as the chat-id to see who participated in the meeting chat. Pass name to keep only members whose displayName or email contains that text (case-insensitive); use the returned userId in an @mention."
1741
1748
  },
1742
1749
  {
@@ -1760,6 +1767,7 @@
1760
1767
  "toolName": "update-chat-message",
1761
1768
  "presets": ["teams", "work"],
1762
1769
  "workScopes": ["Chat.ReadWrite"],
1770
+ "mentionShorthand": true,
1763
1771
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType html in the body."
1764
1772
  },
1765
1773
  {
@@ -1776,6 +1784,7 @@
1776
1784
  "toolName": "send-chat-message",
1777
1785
  "presets": ["teams", "teams-write", "work"],
1778
1786
  "workScopes": ["ChatMessage.Send"],
1787
+ "mentionShorthand": true,
1779
1788
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. Alternatively, hand-build the raw form: put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1780
1789
  },
1781
1790
  {
@@ -1783,7 +1792,8 @@
1783
1792
  "method": "get",
1784
1793
  "toolName": "list-joined-teams",
1785
1794
  "presets": ["teams", "teams-write", "work"],
1786
- "workScopes": ["Team.ReadBasic.All"]
1795
+ "workScopes": ["Team.ReadBasic.All"],
1796
+ "queryParams": []
1787
1797
  },
1788
1798
  {
1789
1799
  "pathPattern": "/teamwork/messaging/customEmojis",
@@ -1792,6 +1802,7 @@
1792
1802
  "apiVersion": "beta",
1793
1803
  "presets": ["teams", "work"],
1794
1804
  "workScopes": ["TeamworkCustomEmoji.Read"],
1805
+ "queryParams": ["top", "filter", "select", "skiptoken"],
1795
1806
  "llmTip": "Lists the organization's custom Teams emojis, including displayName, createdBy and createdDateTime. Graph can return contentBytes: null by default; request select='displayName,contentBytes' (or a string array) to retrieve base64 PNG/GIF images. Supports $top, $filter and $select. Image bytes can make responses large: use a small $top when selecting contentBytes; use fetchAllPages only for an explicitly requested export. displayName is the unique key, not an id. Work/school accounts only. Microsoft Graph beta API: subject to change."
1796
1807
  },
1797
1808
  {
@@ -1865,7 +1876,8 @@
1865
1876
  "method": "get",
1866
1877
  "toolName": "list-channel-messages",
1867
1878
  "presets": ["teams", "work"],
1868
- "workScopes": ["ChannelMessage.Read.All"]
1879
+ "workScopes": ["ChannelMessage.Read.All"],
1880
+ "queryParams": ["top", "expand", "skiptoken"]
1869
1881
  },
1870
1882
  {
1871
1883
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}",
@@ -1880,6 +1892,7 @@
1880
1892
  "toolName": "update-channel-message",
1881
1893
  "presets": ["teams", "work"],
1882
1894
  "workScopes": ["ChannelMessage.ReadWrite"],
1895
+ "mentionShorthand": true,
1883
1896
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType html in the body."
1884
1897
  },
1885
1898
  {
@@ -1896,6 +1909,7 @@
1896
1909
  "toolName": "send-channel-message",
1897
1910
  "presets": ["teams", "teams-write", "work"],
1898
1911
  "workScopes": ["ChannelMessage.Send"],
1912
+ "mentionShorthand": true,
1899
1913
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. Alternatively, hand-build the raw form: put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1900
1914
  },
1901
1915
  {
@@ -1904,6 +1918,7 @@
1904
1918
  "toolName": "reply-to-channel-message",
1905
1919
  "presets": ["teams", "teams-write", "work"],
1906
1920
  "workScopes": ["ChannelMessage.Send"],
1921
+ "mentionShorthand": true,
1907
1922
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. Alternatively, hand-build the raw form: put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1908
1923
  },
1909
1924
  {
@@ -1911,7 +1926,8 @@
1911
1926
  "method": "get",
1912
1927
  "toolName": "list-channel-message-replies",
1913
1928
  "presets": ["teams", "work"],
1914
- "workScopes": ["ChannelMessage.Read.All"]
1929
+ "workScopes": ["ChannelMessage.Read.All"],
1930
+ "queryParams": ["top", "skiptoken"]
1915
1931
  },
1916
1932
  {
1917
1933
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/replies/{chatMessage-id1}",
@@ -1919,6 +1935,7 @@
1919
1935
  "toolName": "update-channel-message-reply",
1920
1936
  "presets": ["teams", "work"],
1921
1937
  "workScopes": ["ChannelMessage.ReadWrite"],
1938
+ "mentionShorthand": true,
1922
1939
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType html in the body."
1923
1940
  },
1924
1941
  {
@@ -1962,6 +1979,7 @@
1962
1979
  "toolName": "list-team-members",
1963
1980
  "presets": ["teams", "work"],
1964
1981
  "workScopes": ["TeamMember.Read.All"],
1982
+ "nameFilter": true,
1965
1983
  "llmTip": "Lists members of a team. Each member has displayName, email, roles and userId. Pass name to keep only members whose displayName or email contains that text (case-insensitive); use the returned userId in an @mention."
1966
1984
  },
1967
1985
  {
@@ -1985,6 +2003,7 @@
1985
2003
  "toolName": "reply-to-chat-message",
1986
2004
  "presets": ["teams", "work"],
1987
2005
  "workScopes": ["ChatMessage.Send"],
2006
+ "mentionShorthand": true,
1988
2007
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. Alternatively, hand-build the raw form: put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1989
2008
  },
1990
2009
  {
@@ -2543,6 +2562,7 @@
2543
2562
  "toolName": "list-calendar-events-delta",
2544
2563
  "presets": ["calendar", "outlook", "personal"],
2545
2564
  "scopes": ["Calendars.Read"],
2565
+ "queryParams": ["startDateTime", "skiptoken"],
2546
2566
  "llmTip": "Incremental sync of events across the default calendar. First call returns all events plus @odata.deltaLink. Subsequent calls with that link return only additions/updates/removals. Deltas expire after ~30 days — start over if the server returns 410 Gone. For a time-bounded view with delta semantics, use list-calendar-view-delta instead."
2547
2567
  },
2548
2568
  {
@@ -2552,6 +2572,7 @@
2552
2572
  "presets": ["calendar", "outlook", "personal"],
2553
2573
  "scopes": ["Calendars.Read"],
2554
2574
  "supportsTimezone": true,
2575
+ "queryParams": ["startDateTime", "endDateTime", "skiptoken"],
2555
2576
  "llmTip": "Incremental sync of events within a time window. Required query params on first call: startDateTime, endDateTime (ISO 8601). Returns events in the window plus @odata.deltaLink; subsequent calls with that link return only changes. Expands recurring events to individual occurrences (unlike list-calendar-events-delta which returns the series master). Use this for calendar UIs showing a week/month view."
2556
2577
  },
2557
2578
  {
@@ -2948,6 +2969,7 @@
2948
2969
  "toolName": "list-my-associated-teams",
2949
2970
  "presets": ["teams", "work"],
2950
2971
  "workScopes": ["Team.ReadBasic.All"],
2972
+ "queryParams": [],
2951
2973
  "llmTip": "Lists Teams the current user is associated with — both joined teams and host teams of shared channels the user is a direct member of. Returns associatedTeamInfo objects with id, displayName, and tenantId. Broader than list-joined-teams because it includes shared-channel host teams."
2952
2974
  },
2953
2975
  {
@@ -36,6 +36,7 @@ import {
36
36
  getMaxPages,
37
37
  isFetchAllPagesApplicable,
38
38
  isSkiptokenApplicable,
39
+ MEMBER_NAME_PARAM_DESCRIPTION,
39
40
  paginationAllowed,
40
41
  positiveIntFromEnv,
41
42
  SKIPTOKEN_PARAM_DESCRIPTION,
@@ -213,8 +214,6 @@ function rewriteMailSearchQuotes(expr) {
213
214
  }
214
215
  return balanceTrailingSlashes(out.trim());
215
216
  }
216
- const MEMBER_NAME_FILTER_TOOLS = /* @__PURE__ */ new Set(["list-team-members", "list-chat-members"]);
217
- const MEMBER_NAME_PARAM_DESCRIPTION = "Case-insensitive text to match against each member's displayName and email. Applied after $select projection, so only fields you can see are matched. Without fetchAllPages only the fetched page is filtered, and @odata.count still reports the unfiltered total.";
218
217
  function filterMembersByName(parsed, name) {
219
218
  const needle = name.trim().toLowerCase();
220
219
  if (needle === "" || !isPlainObject(parsed) || !Array.isArray(parsed.value)) return parsed;
@@ -407,15 +406,6 @@ function collectRecipients(node, domains, counter, depth = 0) {
407
406
  collectRecipients(value, domains, counter, depth + 1);
408
407
  }
409
408
  }
410
- const MENTION_SHORTHAND_TOOLS = /* @__PURE__ */ new Set([
411
- "send-channel-message",
412
- "reply-to-channel-message",
413
- "update-channel-message",
414
- "update-channel-message-reply",
415
- "send-chat-message",
416
- "reply-to-chat-message",
417
- "update-chat-message"
418
- ]);
419
409
  const AAD_GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
420
410
  const SHORTHAND_AT = /<at\s+user-id="([^"]*)">([^<]*)<\/at>/gi;
421
411
  const HAS_SHORTHAND = /<at\b[^>]*\buser-id\b/i;
@@ -1773,10 +1763,10 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
1773
1763
  parameterDefinitions.find((p) => p.type === "Body")?.schema
1774
1764
  );
1775
1765
  const strayBodyFields = {};
1776
- const memberNameFilter = MEMBER_NAME_FILTER_TOOLS.has(tool.alias) && typeof params.name === "string" ? params.name : void 0;
1766
+ const memberNameFilter = config?.nameFilter && typeof params.name === "string" ? params.name : void 0;
1777
1767
  for (const [paramName, paramValue] of Object.entries(params)) {
1778
1768
  if (paramName === "fileName" && config?.blobOutput) continue;
1779
- if (paramName === "name" && MEMBER_NAME_FILTER_TOOLS.has(tool.alias)) continue;
1769
+ if (paramName === "name" && config?.nameFilter) continue;
1780
1770
  if ([
1781
1771
  "account",
1782
1772
  "confirm",
@@ -1811,9 +1801,9 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
1811
1801
  const isQuery = paramDef?.type === "Query" || isOdataParam;
1812
1802
  if (isQuery && paramValue != null && paramValue !== "") {
1813
1803
  const schema = queryParameterSchema(
1814
- tool.alias,
1815
1804
  normalizedParamName,
1816
- paramDef?.schema ?? z.any()
1805
+ paramDef?.schema ?? z.any(),
1806
+ config
1817
1807
  );
1818
1808
  if (!schema || !schema.safeParse(paramValue).success) {
1819
1809
  return {
@@ -1950,7 +1940,7 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
1950
1940
  );
1951
1941
  }
1952
1942
  }
1953
- if (MENTION_SHORTHAND_TOOLS.has(tool.alias)) {
1943
+ if (config?.mentionShorthand) {
1954
1944
  const rewritten = rewriteMentionShorthand(body);
1955
1945
  if (rewritten.error) {
1956
1946
  return {
@@ -2362,7 +2352,7 @@ function registerGraphTools(server, graphClient, options = {}) {
2362
2352
  if (tool.parameters && tool.parameters.length > 0) {
2363
2353
  for (const param of tool.parameters) {
2364
2354
  if (param.type === "Query") {
2365
- const schema = queryParameterSchema(tool.alias, param.name, param.schema || z.any());
2355
+ const schema = queryParameterSchema(param.name, param.schema || z.any(), endpointConfig);
2366
2356
  if (schema) paramSchema[param.name] = schema;
2367
2357
  continue;
2368
2358
  }
@@ -2386,12 +2376,12 @@ function registerGraphTools(server, graphClient, options = {}) {
2386
2376
  }
2387
2377
  if (isFetchAllPagesApplicable(tool)) {
2388
2378
  const maxPages = getMaxPages();
2389
- paramSchema["fetchAllPages"] = z.boolean().describe(getFetchAllPagesParamDescription(maxPages, tool.alias)).optional();
2379
+ paramSchema["fetchAllPages"] = z.boolean().describe(getFetchAllPagesParamDescription(maxPages, endpointConfig)).optional();
2390
2380
  }
2391
- if (MEMBER_NAME_FILTER_TOOLS.has(tool.alias)) {
2381
+ if (endpointConfig?.nameFilter) {
2392
2382
  paramSchema["name"] = z.string().describe(MEMBER_NAME_PARAM_DESCRIPTION).optional();
2393
2383
  }
2394
- if (isSkiptokenApplicable(tool, Object.keys(paramSchema))) {
2384
+ if (isSkiptokenApplicable(tool, Object.keys(paramSchema), endpointConfig)) {
2395
2385
  paramSchema["skiptoken"] = z.string().describe(SKIPTOKEN_PARAM_DESCRIPTION).optional();
2396
2386
  }
2397
2387
  if (multiAccount) {
@@ -1,23 +1,6 @@
1
1
  import logger from "../logger.js";
2
- const SUPPORTED_QUERY_PARAMS = {
3
- // https://learn.microsoft.com/en-us/graph/api/user-list-joinedteams
4
- "list-joined-teams": [],
5
- // https://learn.microsoft.com/en-us/graph/api/associatedteaminfo-list
6
- "list-my-associated-teams": [],
7
- // Documents only $top/$filter; $select is needed to opt in to image bytes.
8
- // https://learn.microsoft.com/graph/api/teamworkmessaging-list-customemojis?view=graph-rest-beta
9
- "list-custom-emojis": ["top", "filter", "select", "skiptoken"],
10
- // https://learn.microsoft.com/en-us/graph/api/channel-list-messages
11
- "list-channel-messages": ["top", "expand", "skiptoken"],
12
- // https://learn.microsoft.com/en-us/graph/api/chatmessage-list-replies
13
- "list-channel-message-replies": ["top", "skiptoken"],
14
- // Delta rejects $select/$expand/$filter/$orderby/$search and documents no $top/$skip/$count;
15
- // events delta takes no endDateTime. https://learn.microsoft.com/graph/api/event-delta?view=graph-rest-beta
16
- "list-calendar-events-delta": ["startdatetime", "skiptoken"],
17
- "list-calendar-view-delta": ["startdatetime", "enddatetime", "skiptoken"]
18
- };
19
- function isQueryParamSupported(toolAlias, bareName) {
20
- return SUPPORTED_QUERY_PARAMS[toolAlias]?.includes(bareName) ?? true;
2
+ function isQueryParamSupported(config, bareName) {
3
+ return config?.queryParams?.some((name) => name.toLowerCase() === bareName) ?? true;
21
4
  }
22
5
  function paginationAllowed() {
23
6
  const raw = process.env.MS365_MCP_ALLOW_PAGINATION;
@@ -41,25 +24,49 @@ function getMaxPages() {
41
24
  function isFetchAllPagesApplicable(tool) {
42
25
  return tool.method.toUpperCase() === "GET" && tool.path.includes("/") && paginationAllowed();
43
26
  }
44
- const FILTER_PARAM_DESCRIPTION = "OData filter expression. Add $count=true for advanced filters (flag/flagStatus, contains()). Cannot combine with $search.";
45
- const SEARCH_PARAM_DESCRIPTION = "KQL search query in one pair of double quotes; directory (users/groups) instead quotes each clause with no outer pair. Cannot combine with $filter.";
27
+ function describeWith(sentences, config) {
28
+ return sentences.filter((s) => typeof s === "string" || isQueryParamSupported(config, s[1])).map((s) => typeof s === "string" ? s : s[0]).join(" ");
29
+ }
30
+ const FILTER_SENTENCES = [
31
+ "OData filter expression.",
32
+ ["Add $count=true for advanced filters (flag/flagStatus, contains()).", "count"],
33
+ ["Cannot combine with $search.", "search"]
34
+ ];
35
+ const SEARCH_SENTENCES = [
36
+ "KQL search query in one pair of double quotes; directory (users/groups) instead quotes each clause with no outer pair.",
37
+ ["Cannot combine with $filter.", "filter"]
38
+ ];
46
39
  const SELECT_PARAM_DESCRIPTION = "Comma-separated fields to return, e.g. id,subject,from,receivedDateTime";
47
- const EXPAND_PARAM_DESCRIPTION = 'Navigation properties to inline, e.g. attachments on a message or event. Only navigation properties can be expanded: expanding a non-navigation property such as a message body fails with "Parsing OData Select and Expand failed". Request ordinary fields with $select instead. Some APIs accept only one relationship per request, so prefer a single value. Some relationships support nested selection, e.g. children($select=id,name).';
40
+ const EXPAND_SENTENCES = [
41
+ "Navigation properties to inline, e.g. attachments on a message or event.",
42
+ 'Only navigation properties can be expanded: expanding a non-navigation property such as a message body fails with "Parsing OData Select and Expand failed".',
43
+ ["Request ordinary fields with $select instead.", "select"],
44
+ "Some APIs accept only one relationship per request, so prefer a single value.",
45
+ "Some relationships support nested selection, e.g. children($select=id,name)."
46
+ ];
48
47
  const ORDERBY_PARAM_DESCRIPTION = "Sort expression, e.g. receivedDateTime desc";
49
- const TOP_PARAM_DESCRIPTION = "Page size (Graph $top). Start small (e.g. 5\u201315) so responses fit the model context; raise only if needed. Use $select to return fewer fields per item. For more rows, use @odata.nextLink from the response instead of a very large $top.";
50
- const SKIP_PARAM_DESCRIPTION = "Items to skip for pagination. Not supported with $search.";
48
+ const TOP_SENTENCES = [
49
+ "Page size (Graph $top).",
50
+ "Start small (e.g. 5\u201315) so responses fit the model context; raise only if needed.",
51
+ ["Use $select to return fewer fields per item.", "select"],
52
+ "For more rows, use @odata.nextLink from the response instead of a very large $top."
53
+ ];
54
+ const SKIP_SENTENCES = [
55
+ "Items to skip for pagination.",
56
+ ["Not supported with $search.", "search"]
57
+ ];
51
58
  const SKIPTOKEN_PARAM_DESCRIPTION = "Next page: the @odata.nextLink from the previous response. Keep the other arguments the same.";
52
59
  const COLLECTION_QUERY_PARAMS = /* @__PURE__ */ new Set(["top", "filter", "orderby", "count"]);
53
- function isSkiptokenApplicable(tool, paramNames) {
60
+ function isSkiptokenApplicable(tool, paramNames, config) {
54
61
  if (tool.method.toUpperCase() !== "GET") return false;
55
- const supported = tool.alias ? SUPPORTED_QUERY_PARAMS[tool.alias] : void 0;
56
- if (supported) return supported.includes("skiptoken");
62
+ if (config?.queryParams) return isQueryParamSupported(config, "skiptoken");
57
63
  for (const name of paramNames) {
58
64
  if (COLLECTION_QUERY_PARAMS.has(name.replace(/^\$/, "").toLowerCase())) return true;
59
65
  }
60
66
  return false;
61
67
  }
62
68
  const COUNT_PARAM_DESCRIPTION = "Set true to enable advanced query mode (ConsistencyLevel: eventual). Required for complex $filter on flag/flagStatus or contains(). The response then includes @odata.count, the total for the filtered collection, which can exceed the items returned when fetchAllPages hits its page/item cap.";
69
+ const MEMBER_NAME_PARAM_DESCRIPTION = "Case-insensitive text to match against each member's displayName and email. Applied after $select projection, so only fields you can see are matched. Without fetchAllPages only the fetched page is filtered, and @odata.count still reports the unfiltered total.";
63
70
  const CONFIRM_PARAM_DESCRIPTION = 'For destructive operations when the confirm gate is enabled (MS365_MCP_REQUIRE_CONFIRM=true; off by default). Set to true only after the user has explicitly approved this action. When the gate is on, calls without confirm: true return { error: "confirmation_required" } without touching user data.';
64
71
  const TIMEZONE_PARAM_DESCRIPTION = 'IANA timezone name (e.g., "America/New_York", "Europe/London", "Asia/Tokyo") for calendar event times. If not specified, times are returned in UTC.';
65
72
  const EXPAND_EXTENDED_PROPERTIES_PARAM_DESCRIPTION = "When true, expands singleValueExtendedProperties on each event. Use this to retrieve custom extended properties (e.g., sync metadata) stored on calendar events.";
@@ -70,26 +77,31 @@ function getAccountParamDescription(accountNames) {
70
77
  const accountHint = accountNames.length > 0 ? `Known accounts: ${accountNames.join(", ")}. ` : "";
71
78
  return `${accountHint}Microsoft account email to use for this request. Required when multiple accounts are configured. Use the list-accounts tool to discover all currently available accounts.`;
72
79
  }
73
- function getFetchAllPagesParamDescription(maxPages, toolAlias) {
74
- const narrowingOptions = toolAlias === "list-custom-emojis" ? "$filter" : "$filter/$search";
75
- return `Follow @odata.nextLink and merge up to ${maxPages} pages into one response. Can return enormous payloads\u2014only when the user explicitly needs a full export. Prefer a small $top first, then paginate or narrow with ${narrowingOptions}.`;
80
+ function getFetchAllPagesParamDescription(maxPages, config) {
81
+ const narrowing = ["filter", "search"].filter((option) => isQueryParamSupported(config, option)).map((option) => `$${option}`).join("/");
82
+ const advice = isQueryParamSupported(config, "top") ? `Prefer a small $top first, then paginate${narrowing ? ` or narrow with ${narrowing}` : ""}.` : narrowing && `Narrow with ${narrowing} first.`;
83
+ return [
84
+ `Follow @odata.nextLink and merge up to ${maxPages} pages into one response.`,
85
+ "Can return enormous payloads\u2014only when the user explicitly needs a full export.",
86
+ advice
87
+ ].filter(Boolean).join(" ");
76
88
  }
77
- function getODataParamDescription(bareName) {
89
+ function getODataParamDescription(bareName, config) {
78
90
  switch (bareName) {
79
91
  case "filter":
80
- return FILTER_PARAM_DESCRIPTION;
92
+ return describeWith(FILTER_SENTENCES, config);
81
93
  case "search":
82
- return SEARCH_PARAM_DESCRIPTION;
94
+ return describeWith(SEARCH_SENTENCES, config);
83
95
  case "select":
84
96
  return SELECT_PARAM_DESCRIPTION;
85
97
  case "expand":
86
- return EXPAND_PARAM_DESCRIPTION;
98
+ return describeWith(EXPAND_SENTENCES, config);
87
99
  case "orderby":
88
100
  return ORDERBY_PARAM_DESCRIPTION;
89
101
  case "top":
90
- return TOP_PARAM_DESCRIPTION;
102
+ return describeWith(TOP_SENTENCES, config);
91
103
  case "skip":
92
- return SKIP_PARAM_DESCRIPTION;
104
+ return describeWith(SKIP_SENTENCES, config);
93
105
  case "count":
94
106
  return COUNT_PARAM_DESCRIPTION;
95
107
  default:
@@ -101,15 +113,11 @@ export {
101
113
  COUNT_PARAM_DESCRIPTION,
102
114
  DEFAULT_MAX_PAGES,
103
115
  EXPAND_EXTENDED_PROPERTIES_PARAM_DESCRIPTION,
104
- EXPAND_PARAM_DESCRIPTION,
105
- FILTER_PARAM_DESCRIPTION,
116
+ MEMBER_NAME_PARAM_DESCRIPTION,
106
117
  ORDERBY_PARAM_DESCRIPTION,
107
- SEARCH_PARAM_DESCRIPTION,
108
118
  SELECT_PARAM_DESCRIPTION,
109
119
  SKIPTOKEN_PARAM_DESCRIPTION,
110
- SKIP_PARAM_DESCRIPTION,
111
120
  TIMEZONE_PARAM_DESCRIPTION,
112
- TOP_PARAM_DESCRIPTION,
113
121
  getAcceptParamDescription,
114
122
  getAccountParamDescription,
115
123
  getFetchAllPagesParamDescription,
@@ -1,13 +1,11 @@
1
1
  import { z } from "zod";
2
- import { getODataParamDescription, isQueryParamSupported } from "./param-descriptions.js";
3
- const CUSTOM_EMOJI_QUERY_DESCRIPTIONS = {
4
- top: "Number of custom emojis to return in one page. Base64 image content is opt-in with select; use a small page size when selecting contentBytes.",
5
- filter: "OData filter expression for custom emojis, forwarded to Microsoft Graph. Filter support is determined by the beta API.",
6
- select: "Properties to return as a comma-separated string or string array. Explicitly select contentBytes to retrieve base64 PNG/GIF images, for example displayName,contentBytes. Graph can return contentBytes: null when it is not explicitly selected."
7
- };
8
- function queryParameterSchema(toolName, name, providerSchema) {
2
+ import {
3
+ getODataParamDescription,
4
+ isQueryParamSupported
5
+ } from "./param-descriptions.js";
6
+ function queryParameterSchema(name, providerSchema, config) {
9
7
  const bareName = name.replace(/^\$/, "").toLowerCase();
10
- if (!isQueryParamSupported(toolName, bareName)) return void 0;
8
+ if (!isQueryParamSupported(config, bareName)) return void 0;
11
9
  const source = providerSchema instanceof z.ZodOptional ? providerSchema.unwrap() : providerSchema;
12
10
  let schema = source;
13
11
  switch (bareName) {
@@ -24,11 +22,11 @@ function queryParameterSchema(toolName, name, providerSchema) {
24
22
  schema = z.string();
25
23
  break;
26
24
  case "top":
27
- if (toolName === "list-chats") schema = source.and(z.number().max(50));
25
+ if (config?.maxTop !== void 0) schema = source.and(z.number().max(config.maxTop));
28
26
  break;
29
27
  }
30
28
  if (providerSchema.isOptional()) schema = schema.optional();
31
- const description = toolName === "list-custom-emojis" && CUSTOM_EMOJI_QUERY_DESCRIPTIONS[bareName] || getODataParamDescription(bareName);
29
+ const description = getODataParamDescription(bareName, config);
32
30
  return description ? schema.describe(description) : schema;
33
31
  }
34
32
  export {
@@ -11,7 +11,8 @@ import {
11
11
  CONFIRM_PARAM_DESCRIPTION,
12
12
  TIMEZONE_PARAM_DESCRIPTION,
13
13
  EXPAND_EXTENDED_PROPERTIES_PARAM_DESCRIPTION,
14
- getAcceptParamDescription
14
+ getAcceptParamDescription,
15
+ MEMBER_NAME_PARAM_DESCRIPTION
15
16
  } from "./param-descriptions.js";
16
17
  function unwrapOptional(schema) {
17
18
  const def = schema._def;
@@ -23,7 +24,7 @@ function unwrapOptional(schema) {
23
24
  }
24
25
  function describeToolSchema(tool, config, ctx = {}) {
25
26
  const params = (tool.parameters ?? []).flatMap((p) => {
26
- const effectiveSchema = p.type === "Query" ? queryParameterSchema(tool.alias, p.name, p.schema) : p.schema;
27
+ const effectiveSchema = p.type === "Query" ? queryParameterSchema(p.name, p.schema, config) : p.schema;
27
28
  if (!effectiveSchema) return [];
28
29
  const { inner, optional } = unwrapOptional(effectiveSchema);
29
30
  const isPath = p.type === "Path";
@@ -53,13 +54,23 @@ function describeToolSchema(tool, config, ctx = {}) {
53
54
  name: "fetchAllPages",
54
55
  in: "Query",
55
56
  required: false,
56
- description: getFetchAllPagesParamDescription(getMaxPages(), tool.alias),
57
+ description: getFetchAllPagesParamDescription(getMaxPages(), config),
57
58
  schema: { type: "boolean" }
58
59
  });
59
60
  }
61
+ if (config?.nameFilter) {
62
+ params.push({
63
+ name: "name",
64
+ in: "Query",
65
+ required: false,
66
+ description: MEMBER_NAME_PARAM_DESCRIPTION,
67
+ schema: { type: "string" }
68
+ });
69
+ }
60
70
  if (isSkiptokenApplicable(
61
- { method: tool.method, alias: tool.alias },
62
- params.map((p) => p.name)
71
+ tool,
72
+ params.map((p) => p.name),
73
+ config
63
74
  )) {
64
75
  params.push({
65
76
  name: "skiptoken",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
3
  "mcpName": "io.github.Softeria/ms-365-mcp-server",
4
- "version": "0.170.0",
4
+ "version": "0.170.1",
5
5
  "description": " A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -1330,7 +1330,8 @@
1330
1330
  "toolName": "list-todo-task-lists",
1331
1331
  "presets": ["personal", "tasks"],
1332
1332
  "scopes": ["Tasks.Read"],
1333
- "llmTip": "Lists all To Do task lists. Returns todoTaskList-id needed for all task operations. The default list is typically called 'Tasks'. NOTE: $select is NOT supported by this endpoint — do not pass select parameter, Graph returns 400."
1333
+ "queryParams": ["top", "skip", "filter", "skiptoken"],
1334
+ "llmTip": "Lists all To Do task lists. Returns todoTaskList-id needed for all task operations. The default list is typically called 'Tasks'."
1334
1335
  },
1335
1336
  {
1336
1337
  "pathPattern": "/me/todo/lists/{todoTaskList-id}/tasks",
@@ -1338,7 +1339,8 @@
1338
1339
  "toolName": "list-todo-tasks",
1339
1340
  "presets": ["personal", "tasks"],
1340
1341
  "scopes": ["Tasks.Read"],
1341
- "llmTip": "Lists tasks in a To Do list. Requires todoTaskList-id — use list-todo-task-lists to find it. NOTE: $select is NOT supported — do not pass select, Graph returns 400. Use $filter=status eq 'notStarted' or $filter=status eq 'completed' to filter by status. Use $top to limit results. Status values: 'notStarted', 'inProgress', 'completed', 'waitingOnOthers', 'deferred'."
1342
+ "queryParams": ["top", "skip", "filter", "orderby", "expand", "skiptoken"],
1343
+ "llmTip": "Lists tasks in a To Do list. Requires todoTaskList-id — use list-todo-task-lists to find it. Use $filter=status eq 'notStarted' or $filter=status eq 'completed' to filter by status. Use $top to limit results. Status values: 'notStarted', 'inProgress', 'completed', 'waitingOnOthers', 'deferred'. $orderby works on createdDateTime, lastModifiedDateTime, importance, status and reminderDateTime/dateTime, not on title or isReminderOn. A $filter on isReminderOn can also return 400; filter it client-side."
1342
1344
  },
1343
1345
  {
1344
1346
  "pathPattern": "/me/todo/lists/{todoTaskList-id}/tasks/{todoTask-id}",
@@ -1346,7 +1348,8 @@
1346
1348
  "toolName": "get-todo-task",
1347
1349
  "presets": ["personal", "tasks"],
1348
1350
  "scopes": ["Tasks.Read"],
1349
- "llmTip": "Returns a single To Do task. NOTE: $select is NOT supported — do not pass select parameter, Graph returns RequestBroker--ParseUri (400). Use $expand=linkedResources to include linked email/resource. Returns body content (HTML format), checklist items, and linked resources."
1351
+ "queryParams": ["expand"],
1352
+ "llmTip": "Returns a single To Do task. Use $expand=linkedResources to include linked email/resource. Returns body content (HTML format), checklist items, and linked resources."
1350
1353
  },
1351
1354
  {
1352
1355
  "pathPattern": "/me/todo/lists/{todoTaskList-id}/tasks",
@@ -1377,6 +1380,7 @@
1377
1380
  "toolName": "list-todo-linked-resources",
1378
1381
  "presets": ["personal", "tasks"],
1379
1382
  "scopes": ["Tasks.Read"],
1383
+ "queryParams": ["top", "skip", "filter", "orderby", "skiptoken"],
1380
1384
  "llmTip": "Lists resources linked to a To Do task (emails, URLs, etc.). Each linked resource has displayName, webUrl, applicationName, and externalId."
1381
1385
  },
1382
1386
  {
@@ -1401,7 +1405,8 @@
1401
1405
  "toolName": "list-todo-checklist-items",
1402
1406
  "presets": ["personal", "tasks"],
1403
1407
  "scopes": ["Tasks.Read"],
1404
- "llmTip": "Lists the steps (checklist items) of a To Do task. Each item has id, displayName, isChecked, checkedDateTime, and createdDateTime. Graph supports no OData query parameters on this endpoint, so do not pass select, filter, orderby, or top. Requires todoTaskListId from list-todo-task-lists and todoTaskId from list-todo-tasks."
1408
+ "queryParams": [],
1409
+ "llmTip": "Lists the steps (checklist items) of a To Do task. Each item has id, displayName, isChecked, checkedDateTime, and createdDateTime. Requires todoTaskListId from list-todo-task-lists and todoTaskId from list-todo-tasks."
1405
1410
  },
1406
1411
  {
1407
1412
  "pathPattern": "/me/todo/lists/{todoTaskList-id}/tasks/{todoTask-id}/checklistItems",
@@ -1722,6 +1727,7 @@
1722
1727
  "toolName": "list-chats",
1723
1728
  "presets": ["teams", "teams-write", "work"],
1724
1729
  "workScopes": ["Chat.ReadBasic"],
1730
+ "maxTop": 50,
1725
1731
  "llmTip": "Lists the signed-in user's chats. Only $filter, $top (max 50), $expand (members, lastMessagePreview) and $orderby are supported; $orderby accepts just lastMessagePreview/createdDateTime in descending order. Other options are rejected: $search returns 400 'Query option Search is not allowed', and ordering by lastUpdatedDateTime returns 400 BadRequest. Meeting chats have chatType=meeting and carry the meeting subject in topic."
1726
1732
  },
1727
1733
  {
@@ -1737,6 +1743,7 @@
1737
1743
  "toolName": "list-chat-members",
1738
1744
  "presets": ["teams", "work"],
1739
1745
  "workScopes": ["ChatMember.Read"],
1746
+ "nameFilter": true,
1740
1747
  "llmTip": "Lists members of a chat. Each member has displayName, email, roles (owner/guest), and visibleHistoryStartDateTime. For meeting chats, use the chatInfo.threadId from get-online-meeting as the chat-id to see who participated in the meeting chat. Pass name to keep only members whose displayName or email contains that text (case-insensitive); use the returned userId in an @mention."
1741
1748
  },
1742
1749
  {
@@ -1760,6 +1767,7 @@
1760
1767
  "toolName": "update-chat-message",
1761
1768
  "presets": ["teams", "work"],
1762
1769
  "workScopes": ["Chat.ReadWrite"],
1770
+ "mentionShorthand": true,
1763
1771
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType html in the body."
1764
1772
  },
1765
1773
  {
@@ -1776,6 +1784,7 @@
1776
1784
  "toolName": "send-chat-message",
1777
1785
  "presets": ["teams", "teams-write", "work"],
1778
1786
  "workScopes": ["ChatMessage.Send"],
1787
+ "mentionShorthand": true,
1779
1788
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. Alternatively, hand-build the raw form: put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1780
1789
  },
1781
1790
  {
@@ -1783,7 +1792,8 @@
1783
1792
  "method": "get",
1784
1793
  "toolName": "list-joined-teams",
1785
1794
  "presets": ["teams", "teams-write", "work"],
1786
- "workScopes": ["Team.ReadBasic.All"]
1795
+ "workScopes": ["Team.ReadBasic.All"],
1796
+ "queryParams": []
1787
1797
  },
1788
1798
  {
1789
1799
  "pathPattern": "/teamwork/messaging/customEmojis",
@@ -1792,6 +1802,7 @@
1792
1802
  "apiVersion": "beta",
1793
1803
  "presets": ["teams", "work"],
1794
1804
  "workScopes": ["TeamworkCustomEmoji.Read"],
1805
+ "queryParams": ["top", "filter", "select", "skiptoken"],
1795
1806
  "llmTip": "Lists the organization's custom Teams emojis, including displayName, createdBy and createdDateTime. Graph can return contentBytes: null by default; request select='displayName,contentBytes' (or a string array) to retrieve base64 PNG/GIF images. Supports $top, $filter and $select. Image bytes can make responses large: use a small $top when selecting contentBytes; use fetchAllPages only for an explicitly requested export. displayName is the unique key, not an id. Work/school accounts only. Microsoft Graph beta API: subject to change."
1796
1807
  },
1797
1808
  {
@@ -1865,7 +1876,8 @@
1865
1876
  "method": "get",
1866
1877
  "toolName": "list-channel-messages",
1867
1878
  "presets": ["teams", "work"],
1868
- "workScopes": ["ChannelMessage.Read.All"]
1879
+ "workScopes": ["ChannelMessage.Read.All"],
1880
+ "queryParams": ["top", "expand", "skiptoken"]
1869
1881
  },
1870
1882
  {
1871
1883
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}",
@@ -1880,6 +1892,7 @@
1880
1892
  "toolName": "update-channel-message",
1881
1893
  "presets": ["teams", "work"],
1882
1894
  "workScopes": ["ChannelMessage.ReadWrite"],
1895
+ "mentionShorthand": true,
1883
1896
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType html in the body."
1884
1897
  },
1885
1898
  {
@@ -1896,6 +1909,7 @@
1896
1909
  "toolName": "send-channel-message",
1897
1910
  "presets": ["teams", "teams-write", "work"],
1898
1911
  "workScopes": ["ChannelMessage.Send"],
1912
+ "mentionShorthand": true,
1899
1913
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. Alternatively, hand-build the raw form: put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1900
1914
  },
1901
1915
  {
@@ -1904,6 +1918,7 @@
1904
1918
  "toolName": "reply-to-channel-message",
1905
1919
  "presets": ["teams", "teams-write", "work"],
1906
1920
  "workScopes": ["ChannelMessage.Send"],
1921
+ "mentionShorthand": true,
1907
1922
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. Alternatively, hand-build the raw form: put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1908
1923
  },
1909
1924
  {
@@ -1911,7 +1926,8 @@
1911
1926
  "method": "get",
1912
1927
  "toolName": "list-channel-message-replies",
1913
1928
  "presets": ["teams", "work"],
1914
- "workScopes": ["ChannelMessage.Read.All"]
1929
+ "workScopes": ["ChannelMessage.Read.All"],
1930
+ "queryParams": ["top", "skiptoken"]
1915
1931
  },
1916
1932
  {
1917
1933
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/replies/{chatMessage-id1}",
@@ -1919,6 +1935,7 @@
1919
1935
  "toolName": "update-channel-message-reply",
1920
1936
  "presets": ["teams", "work"],
1921
1937
  "workScopes": ["ChannelMessage.ReadWrite"],
1938
+ "mentionShorthand": true,
1922
1939
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType html in the body."
1923
1940
  },
1924
1941
  {
@@ -1962,6 +1979,7 @@
1962
1979
  "toolName": "list-team-members",
1963
1980
  "presets": ["teams", "work"],
1964
1981
  "workScopes": ["TeamMember.Read.All"],
1982
+ "nameFilter": true,
1965
1983
  "llmTip": "Lists members of a team. Each member has displayName, email, roles and userId. Pass name to keep only members whose displayName or email contains that text (case-insensitive); use the returned userId in an @mention."
1966
1984
  },
1967
1985
  {
@@ -1985,6 +2003,7 @@
1985
2003
  "toolName": "reply-to-chat-message",
1986
2004
  "presets": ["teams", "work"],
1987
2005
  "workScopes": ["ChatMessage.Send"],
2006
+ "mentionShorthand": true,
1988
2007
  "llmTip": "To @mention someone, put <at user-id=\"<aad-user-id>\">Display Name</at> in the html content; the server numbers the tag and builds the mentions array. Use the userId field (not id) from list-team-members or list-chat-members. Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. Alternatively, hand-build the raw form: put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1989
2008
  },
1990
2009
  {
@@ -2543,6 +2562,7 @@
2543
2562
  "toolName": "list-calendar-events-delta",
2544
2563
  "presets": ["calendar", "outlook", "personal"],
2545
2564
  "scopes": ["Calendars.Read"],
2565
+ "queryParams": ["startDateTime", "skiptoken"],
2546
2566
  "llmTip": "Incremental sync of events across the default calendar. First call returns all events plus @odata.deltaLink. Subsequent calls with that link return only additions/updates/removals. Deltas expire after ~30 days — start over if the server returns 410 Gone. For a time-bounded view with delta semantics, use list-calendar-view-delta instead."
2547
2567
  },
2548
2568
  {
@@ -2552,6 +2572,7 @@
2552
2572
  "presets": ["calendar", "outlook", "personal"],
2553
2573
  "scopes": ["Calendars.Read"],
2554
2574
  "supportsTimezone": true,
2575
+ "queryParams": ["startDateTime", "endDateTime", "skiptoken"],
2555
2576
  "llmTip": "Incremental sync of events within a time window. Required query params on first call: startDateTime, endDateTime (ISO 8601). Returns events in the window plus @odata.deltaLink; subsequent calls with that link return only changes. Expands recurring events to individual occurrences (unlike list-calendar-events-delta which returns the series master). Use this for calendar UIs showing a week/month view."
2556
2577
  },
2557
2578
  {
@@ -2948,6 +2969,7 @@
2948
2969
  "toolName": "list-my-associated-teams",
2949
2970
  "presets": ["teams", "work"],
2950
2971
  "workScopes": ["Team.ReadBasic.All"],
2972
+ "queryParams": [],
2951
2973
  "llmTip": "Lists Teams the current user is associated with — both joined teams and host teams of shared channels the user is a direct member of. Returns associatedTeamInfo objects with id, displayName, and tenantId. Broader than list-joined-teams because it includes shared-channel host teams."
2952
2974
  },
2953
2975
  {