@softeria/ms-365-mcp-server 0.169.3 → 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.
- package/.env.example +6 -1
- package/README.md +9 -6
- package/dist/__tests__/graph-tools.test.js +22 -11
- package/dist/cli.js +4 -1
- package/dist/cloud-config.js +12 -1
- package/dist/endpoints.json +29 -7
- package/dist/graph-tools.js +10 -20
- package/dist/lib/param-descriptions.js +49 -41
- package/dist/lib/query-parameter-schema.js +8 -10
- package/dist/lib/tool-schema.js +16 -5
- package/package.json +1 -1
- package/src/endpoints.json +29 -7
package/.env.example
CHANGED
|
@@ -15,7 +15,7 @@ MS365_MCP_CLIENT_SECRET=your-azure-ad-app-client-secret-here
|
|
|
15
15
|
# Tenant ID - use "common" for multi-tenant or your specific tenant ID
|
|
16
16
|
MS365_MCP_TENANT_ID=common
|
|
17
17
|
|
|
18
|
-
# Cloud environment: global (default)
|
|
18
|
+
# Cloud environment: global (default), china (21Vianet) or gcc-high (US Government)
|
|
19
19
|
# MS365_MCP_CLOUD_TYPE=global
|
|
20
20
|
|
|
21
21
|
# Instructions for Global Cloud:
|
|
@@ -37,6 +37,11 @@ MS365_MCP_TENANT_ID=common
|
|
|
37
37
|
# 3. Follow the same steps as above
|
|
38
38
|
# 4. Set MS365_MCP_CLOUD_TYPE=china
|
|
39
39
|
|
|
40
|
+
# Instructions for GCC High:
|
|
41
|
+
# 1. Go to https://portal.azure.us
|
|
42
|
+
# 2. Register an app in your government tenant (no default client ID exists)
|
|
43
|
+
# 3. Set MS365_MCP_CLIENT_ID, MS365_MCP_TENANT_ID and MS365_MCP_CLOUD_TYPE=gcc-high
|
|
44
|
+
|
|
40
45
|
# -------------------------------------------------------------------
|
|
41
46
|
# Azure Key Vault Integration (Optional)
|
|
42
47
|
# -------------------------------------------------------------------
|
package/README.md
CHANGED
|
@@ -11,10 +11,13 @@ API.
|
|
|
11
11
|
|
|
12
12
|
This server supports multiple Microsoft cloud environments:
|
|
13
13
|
|
|
14
|
-
| Cloud | Description
|
|
15
|
-
| -------------------- |
|
|
16
|
-
| **Global** (default) | International Microsoft 365
|
|
17
|
-
| **China** (21Vianet) | Microsoft 365 operated by 21Vianet
|
|
14
|
+
| Cloud | Description | Auth Endpoint | Graph API Endpoint |
|
|
15
|
+
| -------------------- | ------------------------------------ | ------------------------- | ------------------------------- |
|
|
16
|
+
| **Global** (default) | International Microsoft 365 | login.microsoftonline.com | graph.microsoft.com |
|
|
17
|
+
| **China** (21Vianet) | Microsoft 365 operated by 21Vianet | login.chinacloudapi.cn | microsoftgraph.chinacloudapi.cn |
|
|
18
|
+
| **GCC High** | Microsoft 365 US Government GCC High | login.microsoftonline.us | graph.microsoft.us |
|
|
19
|
+
|
|
20
|
+
GCC High has no pre-registered client: register an app in your government tenant and set `MS365_MCP_CLIENT_ID` (and `MS365_MCP_TENANT_ID`), then use `--cloud gcc-high`.
|
|
18
21
|
|
|
19
22
|
To route Graph traffic through a proxy of your own (one that terminates the client's plain HTTP and originates TLS to Microsoft), set `MS365_MCP_GRAPH_BASE_URL` to its base URL; see the environment variable list under CLI Options, including what still bypasses it.
|
|
20
23
|
|
|
@@ -673,7 +676,7 @@ The following options can be used when running ms-365-mcp-server directly from t
|
|
|
673
676
|
--org-mode Enable organization/work mode from start (includes Teams, SharePoint, etc.)
|
|
674
677
|
--work-mode Alias for --org-mode
|
|
675
678
|
--force-work-scopes Backwards compatibility alias for --org-mode (deprecated)
|
|
676
|
-
--cloud <type> Microsoft cloud environment: global (default)
|
|
679
|
+
--cloud <type> Microsoft cloud environment: global (default), china (21Vianet) or gcc-high
|
|
677
680
|
--allowed-scopes <scopes> Limit exposed tools to Graph scopes covered by this allowlist
|
|
678
681
|
--extra-scopes <scopes> Append additional Graph scopes to the token request (for use with your own app registration + graph-batch)
|
|
679
682
|
--expected-username <username> Require local MSAL auth to use this account username
|
|
@@ -745,7 +748,7 @@ Environment variables:
|
|
|
745
748
|
- `MS365_MCP_ATTACHMENT_HOST=<host>`: Interface the `MS365_MCP_ATTACHMENT_PORT` listener binds (alternative to --attachment-host; requires `--attachment-port`). Defaults to the host `--http` bound — which for a wildcard `--http` means both ports answer everywhere and the port split isolates nothing. See "Splitting the attachment listener"
|
|
746
749
|
- `MS365_MCP_HTTP_LOCAL_FILE_TOOLS=true|1`: Register download-bytes-to-file, and upload-attachment-from-file when `MS365_MCP_UPLOAD_DIRS` is set, over HTTP (alternative to --http-local-file-tools; same restrictions)
|
|
747
750
|
- `MS365_MCP_UPLOAD_DIRS=<dirs>`: Directories `upload-attachment-from-file` may read from, as absolute paths separated like `PATH`. Unset (the default) leaves the tool unregistered. See "Attaching a local file in stdio mode"
|
|
748
|
-
- `MS365_MCP_CLOUD_TYPE=global|china`: Microsoft cloud environment (alternative to --cloud flag)
|
|
751
|
+
- `MS365_MCP_CLOUD_TYPE=global|china|gcc-high`: Microsoft cloud environment (alternative to --cloud flag)
|
|
749
752
|
- `MS365_MCP_GRAPH_BASE_URL=<url>`: Send Graph API requests to this base URL instead of the cloud's `graph.microsoft.com` (e.g. `http://127.0.0.1:10255/tenant-a/graph` for an egress proxy that originates TLS itself). Absolute http(s) URL without query or fragment; a path prefix is kept, so requests go to `<url>/v1.0/...`. The login authority and the On-Behalf-Of token resource are unchanged. Two things bypass it: `get-download-url` returns Graph's pre-authenticated SharePoint URL, and the `302` from `/content` points straight at SharePoint, so whoever follows either connects to SharePoint directly; if you control egress, allow those SharePoint hosts or disable those tools. Not read from `.env`
|
|
750
753
|
- `LOG_LEVEL`: Set logging level (default: 'info')
|
|
751
754
|
- `SILENT=true|1`: Disable console output
|
|
@@ -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 = [
|
|
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({
|
|
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
|
|
4339
|
-
const { graphClient } = await call(
|
|
4340
|
-
|
|
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();
|
package/dist/cli.js
CHANGED
|
@@ -55,7 +55,10 @@ program.name("ms-365-mcp-server").description("Microsoft 365 MCP Server").versio
|
|
|
55
55
|
).option("--list-presets", "List all available presets and exit").option("--list-permissions", "List all required Graph API permissions and exit").option(
|
|
56
56
|
"--org-mode",
|
|
57
57
|
"Enable organization/work mode from start (includes Teams, SharePoint, etc.)"
|
|
58
|
-
).option("--work-mode", "Alias for --org-mode").option("--force-work-scopes", "Backwards compatibility alias for --org-mode (deprecated)").option("--toon", "(experimental) Enable TOON output format for 30-60% token reduction").option("--discovery", "Enable runtime tool discovery and loading (experimental feature)").option(
|
|
58
|
+
).option("--work-mode", "Alias for --org-mode").option("--force-work-scopes", "Backwards compatibility alias for --org-mode (deprecated)").option("--toon", "(experimental) Enable TOON output format for 30-60% token reduction").option("--discovery", "Enable runtime tool discovery and loading (experimental feature)").option(
|
|
59
|
+
"--cloud <type>",
|
|
60
|
+
"Microsoft cloud environment: global (default), china (21Vianet) or gcc-high (US Government GCC High)"
|
|
61
|
+
).option(
|
|
59
62
|
"--enable-dynamic-registration",
|
|
60
63
|
"Enable OAuth Dynamic Client Registration endpoint (kept for backwards compatibility, now enabled by default in HTTP mode)"
|
|
61
64
|
).option(
|
package/dist/cloud-config.js
CHANGED
|
@@ -8,6 +8,11 @@ const CLOUD_ENDPOINTS = {
|
|
|
8
8
|
authority: "https://login.chinacloudapi.cn",
|
|
9
9
|
graphApi: "https://microsoftgraph.chinacloudapi.cn",
|
|
10
10
|
portal: "https://portal.azure.cn"
|
|
11
|
+
},
|
|
12
|
+
"gcc-high": {
|
|
13
|
+
authority: "https://login.microsoftonline.us",
|
|
14
|
+
graphApi: "https://graph.microsoft.us",
|
|
15
|
+
portal: "https://portal.azure.us"
|
|
11
16
|
}
|
|
12
17
|
};
|
|
13
18
|
const DEFAULT_CLIENT_IDS = {
|
|
@@ -15,7 +20,13 @@ const DEFAULT_CLIENT_IDS = {
|
|
|
15
20
|
china: "f3e61a6e-bc26-4281-8588-2c7359a02141"
|
|
16
21
|
};
|
|
17
22
|
function getDefaultClientId(cloudType = "global") {
|
|
18
|
-
|
|
23
|
+
const clientId = DEFAULT_CLIENT_IDS[cloudType];
|
|
24
|
+
if (!clientId) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`No default client ID exists for the ${cloudType} cloud. Register an app in the ${cloudType} tenant and set MS365_MCP_CLIENT_ID.`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return clientId;
|
|
19
30
|
}
|
|
20
31
|
const GRAPH_BASE_URL_ENV = "MS365_MCP_GRAPH_BASE_URL";
|
|
21
32
|
function getGraphBaseUrlOverride() {
|
package/dist/endpoints.json
CHANGED
|
@@ -1330,7 +1330,8 @@
|
|
|
1330
1330
|
"toolName": "list-todo-task-lists",
|
|
1331
1331
|
"presets": ["personal", "tasks"],
|
|
1332
1332
|
"scopes": ["Tasks.Read"],
|
|
1333
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
{
|
package/dist/graph-tools.js
CHANGED
|
@@ -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 =
|
|
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" &&
|
|
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 (
|
|
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(
|
|
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,
|
|
2379
|
+
paramSchema["fetchAllPages"] = z.boolean().describe(getFetchAllPagesParamDescription(maxPages, endpointConfig)).optional();
|
|
2390
2380
|
}
|
|
2391
|
-
if (
|
|
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
|
-
|
|
3
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
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
|
|
50
|
-
|
|
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
|
-
|
|
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,
|
|
74
|
-
const
|
|
75
|
-
|
|
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
|
|
92
|
+
return describeWith(FILTER_SENTENCES, config);
|
|
81
93
|
case "search":
|
|
82
|
-
return
|
|
94
|
+
return describeWith(SEARCH_SENTENCES, config);
|
|
83
95
|
case "select":
|
|
84
96
|
return SELECT_PARAM_DESCRIPTION;
|
|
85
97
|
case "expand":
|
|
86
|
-
return
|
|
98
|
+
return describeWith(EXPAND_SENTENCES, config);
|
|
87
99
|
case "orderby":
|
|
88
100
|
return ORDERBY_PARAM_DESCRIPTION;
|
|
89
101
|
case "top":
|
|
90
|
-
return
|
|
102
|
+
return describeWith(TOP_SENTENCES, config);
|
|
91
103
|
case "skip":
|
|
92
|
-
return
|
|
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
|
-
|
|
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 {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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(
|
|
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 (
|
|
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 =
|
|
29
|
+
const description = getODataParamDescription(bareName, config);
|
|
32
30
|
return description ? schema.describe(description) : schema;
|
|
33
31
|
}
|
|
34
32
|
export {
|
package/dist/lib/tool-schema.js
CHANGED
|
@@ -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(
|
|
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(),
|
|
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
|
-
|
|
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.
|
|
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",
|
package/src/endpoints.json
CHANGED
|
@@ -1330,7 +1330,8 @@
|
|
|
1330
1330
|
"toolName": "list-todo-task-lists",
|
|
1331
1331
|
"presets": ["personal", "tasks"],
|
|
1332
1332
|
"scopes": ["Tasks.Read"],
|
|
1333
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
{
|