@softeria/ms-365-mcp-server 0.166.0 → 0.167.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/graph-tools.test.js +144 -0
- package/dist/endpoints.json +3 -2
- package/dist/graph-tools.js +43 -3
- package/dist/lib/log-redactor.js +23 -6
- package/package.json +1 -1
- package/src/endpoints.json +3 -2
|
@@ -4190,4 +4190,148 @@ describe("graph-tools", () => {
|
|
|
4190
4190
|
expect(isDestructiveOperation("DELETE", { readOnly: true })).toBe(true);
|
|
4191
4191
|
});
|
|
4192
4192
|
});
|
|
4193
|
+
describe("filterMembersByName", () => {
|
|
4194
|
+
const body = {
|
|
4195
|
+
value: [
|
|
4196
|
+
{ id: "1", displayName: "Jane Doe", email: "jane@contoso.com" },
|
|
4197
|
+
{ id: "2", displayName: "John Smith", email: "jsmith@contoso.com" },
|
|
4198
|
+
{ id: "3", displayName: null, email: null }
|
|
4199
|
+
],
|
|
4200
|
+
"@odata.count": 3
|
|
4201
|
+
};
|
|
4202
|
+
const ids = (r) => r.value.map((m) => m.id);
|
|
4203
|
+
it("matches displayName case-insensitively", async () => {
|
|
4204
|
+
const { filterMembersByName } = await loadModule();
|
|
4205
|
+
expect(ids(filterMembersByName(body, "JANE"))).toEqual(["1"]);
|
|
4206
|
+
});
|
|
4207
|
+
it("matches email", async () => {
|
|
4208
|
+
const { filterMembersByName } = await loadModule();
|
|
4209
|
+
expect(ids(filterMembersByName(body, "jsmith@"))).toEqual(["2"]);
|
|
4210
|
+
});
|
|
4211
|
+
it("returns an empty value array when nothing matches", async () => {
|
|
4212
|
+
const { filterMembersByName } = await loadModule();
|
|
4213
|
+
expect(ids(filterMembersByName(body, "zzz"))).toEqual([]);
|
|
4214
|
+
});
|
|
4215
|
+
it("returns everything for an empty or whitespace-only name", async () => {
|
|
4216
|
+
const { filterMembersByName } = await loadModule();
|
|
4217
|
+
expect(ids(filterMembersByName(body, " "))).toEqual(["1", "2", "3"]);
|
|
4218
|
+
});
|
|
4219
|
+
it("does not match or throw on null fields", async () => {
|
|
4220
|
+
const { filterMembersByName } = await loadModule();
|
|
4221
|
+
expect(ids(filterMembersByName(body, "null"))).toEqual([]);
|
|
4222
|
+
});
|
|
4223
|
+
it("keeps other top-level fields", async () => {
|
|
4224
|
+
const { filterMembersByName } = await loadModule();
|
|
4225
|
+
expect(filterMembersByName(body, "jane")["@odata.count"]).toBe(3);
|
|
4226
|
+
});
|
|
4227
|
+
it("returns non-collection input unchanged", async () => {
|
|
4228
|
+
const { filterMembersByName } = await loadModule();
|
|
4229
|
+
expect(filterMembersByName("plain", "x")).toBe("plain");
|
|
4230
|
+
expect(filterMembersByName({ id: "1" }, "x")).toEqual({ id: "1" });
|
|
4231
|
+
});
|
|
4232
|
+
});
|
|
4233
|
+
describe("member name filter (tool wiring)", () => {
|
|
4234
|
+
const membersBody = {
|
|
4235
|
+
value: [
|
|
4236
|
+
{ id: "1", displayName: "Jane Doe", email: "jane@contoso.com" },
|
|
4237
|
+
{ id: "2", displayName: "John Smith", email: "jsmith@contoso.com" },
|
|
4238
|
+
{ id: "3", displayName: null, email: null }
|
|
4239
|
+
],
|
|
4240
|
+
"@odata.count": 3
|
|
4241
|
+
};
|
|
4242
|
+
const page = (b) => ({ content: [{ type: "text", text: JSON.stringify(b) }] });
|
|
4243
|
+
const ids = (result) => JSON.parse(result.content[0].text).value.map((m) => m.id);
|
|
4244
|
+
async function run(args, responses = [page(membersBody)], alias = "list-team-members", path = "/teams/{team-id}/members", outputFormat = "json") {
|
|
4245
|
+
mockEndpoints.push(makeEndpoint({ alias, path }));
|
|
4246
|
+
mockEndpointsJson = [makeConfig({ toolName: alias, pathPattern: path })];
|
|
4247
|
+
const graphClient = createMockGraphClient(responses, outputFormat);
|
|
4248
|
+
const server = createMockServer();
|
|
4249
|
+
const { registerGraphTools } = await loadModule();
|
|
4250
|
+
registerGraphTools(server, graphClient);
|
|
4251
|
+
const result = await server.tools.get(alias).handler({ "team-id": "t1", ...args });
|
|
4252
|
+
return { result, graphClient };
|
|
4253
|
+
}
|
|
4254
|
+
it("filters list-team-members by name", async () => {
|
|
4255
|
+
const { result } = await run({ name: "jane" });
|
|
4256
|
+
expect(ids(result)).toEqual(["1"]);
|
|
4257
|
+
});
|
|
4258
|
+
it("filters list-chat-members by email", async () => {
|
|
4259
|
+
const { result } = await run(
|
|
4260
|
+
{ "chat-id": "c1", name: "jsmith" },
|
|
4261
|
+
[page(membersBody)],
|
|
4262
|
+
"list-chat-members",
|
|
4263
|
+
"/chats/{chat-id}/members"
|
|
4264
|
+
);
|
|
4265
|
+
expect(ids(result)).toEqual(["2"]);
|
|
4266
|
+
});
|
|
4267
|
+
it("does not send name to Graph", async () => {
|
|
4268
|
+
const { graphClient } = await run({ name: "jane" });
|
|
4269
|
+
expect(graphClient.graphRequest.mock.calls[0][0]).not.toContain("name");
|
|
4270
|
+
});
|
|
4271
|
+
it("returns the full list for a whitespace-only name", async () => {
|
|
4272
|
+
const { result } = await run({ name: " " });
|
|
4273
|
+
expect(ids(result)).toEqual(["1", "2", "3"]);
|
|
4274
|
+
});
|
|
4275
|
+
it("leaves an error response untouched", async () => {
|
|
4276
|
+
const err = { content: [{ type: "text", text: '{"error":"nope"}' }], isError: true };
|
|
4277
|
+
const { result } = await run({ name: "jane" }, [err]);
|
|
4278
|
+
expect(result.content[0].text).toBe('{"error":"nope"}');
|
|
4279
|
+
});
|
|
4280
|
+
it("keeps @odata.count as the unfiltered total", async () => {
|
|
4281
|
+
const { result } = await run({ name: "jane" });
|
|
4282
|
+
expect(JSON.parse(result.content[0].text)["@odata.count"]).toBe(3);
|
|
4283
|
+
});
|
|
4284
|
+
it("filters after fetchAllPages merges the pages", async () => {
|
|
4285
|
+
const { result } = await run({ name: "ann", fetchAllPages: true }, [
|
|
4286
|
+
page({
|
|
4287
|
+
value: [{ id: "1", displayName: "Ann", email: "a@x.com" }],
|
|
4288
|
+
"@odata.nextLink": "https://graph.microsoft.com/v1.0/teams/t1/members?$skiptoken=x"
|
|
4289
|
+
}),
|
|
4290
|
+
page({
|
|
4291
|
+
value: [
|
|
4292
|
+
{ id: "2", displayName: "Bob", email: "b@x.com" },
|
|
4293
|
+
{ id: "3", displayName: "Annette", email: "n@x.com" }
|
|
4294
|
+
]
|
|
4295
|
+
})
|
|
4296
|
+
]);
|
|
4297
|
+
expect(ids(result)).toEqual(["1", "3"]);
|
|
4298
|
+
});
|
|
4299
|
+
it("matches only fields that survive $select projection", async () => {
|
|
4300
|
+
const { result } = await run({ select: "id,displayName", name: "contoso" });
|
|
4301
|
+
expect(ids(result)).toEqual([]);
|
|
4302
|
+
});
|
|
4303
|
+
describe("under --toon", () => {
|
|
4304
|
+
const parseToon = (result) => JSON.parse(result.content[0].text.replace(/^TOON:/, ""));
|
|
4305
|
+
const toon = (args, responses = [page(membersBody)]) => run(args, responses, "list-team-members", "/teams/{team-id}/members", "toon");
|
|
4306
|
+
it("filters when no projection or merge applies", async () => {
|
|
4307
|
+
const { result } = await toon({ name: "jane" });
|
|
4308
|
+
expect(parseToon(result).value.map((m) => m.id)).toEqual(["1"]);
|
|
4309
|
+
});
|
|
4310
|
+
it("filters when $select projects the body", async () => {
|
|
4311
|
+
const { result } = await toon({ select: "id,displayName", name: "jane" });
|
|
4312
|
+
expect(parseToon(result).value).toEqual([{ id: "1", displayName: "Jane Doe" }]);
|
|
4313
|
+
});
|
|
4314
|
+
it("filters after fetchAllPages merges the pages", async () => {
|
|
4315
|
+
const { result } = await toon({ name: "ann", fetchAllPages: true }, [
|
|
4316
|
+
page({
|
|
4317
|
+
value: [{ id: "1", displayName: "Ann", email: "a@x.com" }],
|
|
4318
|
+
"@odata.nextLink": "https://graph.microsoft.com/v1.0/teams/t1/members?$skiptoken=x"
|
|
4319
|
+
}),
|
|
4320
|
+
page({
|
|
4321
|
+
value: [
|
|
4322
|
+
{ id: "2", displayName: "Bob", email: "b@x.com" },
|
|
4323
|
+
{ id: "3", displayName: "Annette", email: "n@x.com" }
|
|
4324
|
+
]
|
|
4325
|
+
})
|
|
4326
|
+
]);
|
|
4327
|
+
expect(parseToon(result).value.map((m) => m.id)).toEqual(["1", "3"]);
|
|
4328
|
+
});
|
|
4329
|
+
});
|
|
4330
|
+
it("still matches a projected field", async () => {
|
|
4331
|
+
const { result } = await run({ select: "id,displayName", name: "jane" });
|
|
4332
|
+
expect(JSON.parse(result.content[0].text).value).toEqual([
|
|
4333
|
+
{ id: "1", displayName: "Jane Doe" }
|
|
4334
|
+
]);
|
|
4335
|
+
});
|
|
4336
|
+
});
|
|
4193
4337
|
});
|
package/dist/endpoints.json
CHANGED
|
@@ -1737,7 +1737,7 @@
|
|
|
1737
1737
|
"toolName": "list-chat-members",
|
|
1738
1738
|
"presets": ["teams", "work"],
|
|
1739
1739
|
"workScopes": ["ChatMember.Read"],
|
|
1740
|
-
"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."
|
|
1740
|
+
"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
1741
|
},
|
|
1742
1742
|
{
|
|
1743
1743
|
"pathPattern": "/chats/{chat-id}/messages",
|
|
@@ -1958,7 +1958,8 @@
|
|
|
1958
1958
|
"method": "get",
|
|
1959
1959
|
"toolName": "list-team-members",
|
|
1960
1960
|
"presets": ["teams", "work"],
|
|
1961
|
-
"workScopes": ["TeamMember.Read.All"]
|
|
1961
|
+
"workScopes": ["TeamMember.Read.All"],
|
|
1962
|
+
"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."
|
|
1962
1963
|
},
|
|
1963
1964
|
{
|
|
1964
1965
|
"pathPattern": "/teams/{team-id}/channels/{channel-id}/filesFolder",
|
package/dist/graph-tools.js
CHANGED
|
@@ -203,6 +203,17 @@ function rewriteMailSearchQuotes(expr) {
|
|
|
203
203
|
}
|
|
204
204
|
return balanceTrailingSlashes(out.trim());
|
|
205
205
|
}
|
|
206
|
+
const MEMBER_NAME_FILTER_TOOLS = /* @__PURE__ */ new Set(["list-team-members", "list-chat-members"]);
|
|
207
|
+
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.";
|
|
208
|
+
function filterMembersByName(parsed, name) {
|
|
209
|
+
const needle = name.trim().toLowerCase();
|
|
210
|
+
if (needle === "" || !isPlainObject(parsed) || !Array.isArray(parsed.value)) return parsed;
|
|
211
|
+
const matches = (member) => isPlainObject(member) && ["displayName", "email"].some((field) => {
|
|
212
|
+
const value = member[field];
|
|
213
|
+
return typeof value === "string" && value.toLowerCase().includes(needle);
|
|
214
|
+
});
|
|
215
|
+
return { ...parsed, value: parsed.value.filter(matches) };
|
|
216
|
+
}
|
|
206
217
|
function normalizeSearchQueryParam(queryParams, path2, toolAlias) {
|
|
207
218
|
if (!isOutlookMailPath(path2)) return;
|
|
208
219
|
const raw = queryParams["$search"];
|
|
@@ -1541,8 +1552,10 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
|
|
|
1541
1552
|
parameterDefinitions.find((p) => p.type === "Body")?.schema
|
|
1542
1553
|
);
|
|
1543
1554
|
const strayBodyFields = {};
|
|
1555
|
+
const memberNameFilter = MEMBER_NAME_FILTER_TOOLS.has(tool.alias) && typeof params.name === "string" ? params.name : void 0;
|
|
1544
1556
|
for (const [paramName, paramValue] of Object.entries(params)) {
|
|
1545
1557
|
if (paramName === "fileName" && config?.blobOutput) continue;
|
|
1558
|
+
if (paramName === "name" && MEMBER_NAME_FILTER_TOOLS.has(tool.alias)) continue;
|
|
1546
1559
|
if ([
|
|
1547
1560
|
"account",
|
|
1548
1561
|
"confirm",
|
|
@@ -1848,12 +1861,23 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
|
|
|
1848
1861
|
const mergePages = fetchAllPages && paginationEnabled;
|
|
1849
1862
|
const willProject = (requestedSelect.length > 0 || isUserFieldBoundary) && params.excludeResponse !== true;
|
|
1850
1863
|
const willProjectBatch = batchRestricted.size > 0 && params.excludeResponse !== true;
|
|
1851
|
-
|
|
1864
|
+
const willFilterMembers = memberNameFilter !== void 0 && memberNameFilter.trim() !== "" && params.excludeResponse !== true;
|
|
1865
|
+
if (mergePages || willProject || willProjectBatch || willFilterMembers) {
|
|
1852
1866
|
options.forceJsonOutput = true;
|
|
1853
1867
|
}
|
|
1854
1868
|
let response = await graphClient.graphRequest(path2, options);
|
|
1855
1869
|
const shouldProject = willProject && !response?.isError;
|
|
1856
1870
|
let projectionHandled = false;
|
|
1871
|
+
let memberFilterHandled = false;
|
|
1872
|
+
const applyMemberFilter = (body2) => {
|
|
1873
|
+
if (!willFilterMembers) return body2;
|
|
1874
|
+
memberFilterHandled = true;
|
|
1875
|
+
const filtered = filterMembersByName(body2, memberNameFilter);
|
|
1876
|
+
if (isPlainObject(filtered) && Array.isArray(filtered.value) && response._meta) {
|
|
1877
|
+
response._meta = { ...response._meta, result_count: filtered.value.length };
|
|
1878
|
+
}
|
|
1879
|
+
return filtered;
|
|
1880
|
+
};
|
|
1857
1881
|
const applyProjection = (body2) => {
|
|
1858
1882
|
if (isTransportEnvelope(body2)) {
|
|
1859
1883
|
logger.info("Skipping $select projection: body is a transport envelope, not a resource");
|
|
@@ -1948,7 +1972,9 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
|
|
|
1948
1972
|
logger.error(`Error during pagination: ${e}`);
|
|
1949
1973
|
}
|
|
1950
1974
|
if (combinedResponse !== void 0) {
|
|
1951
|
-
const merged =
|
|
1975
|
+
const merged = applyMemberFilter(
|
|
1976
|
+
shouldProject ? applyProjection(combinedResponse) : combinedResponse
|
|
1977
|
+
);
|
|
1952
1978
|
projectionHandled = shouldProject;
|
|
1953
1979
|
response.content[0].text = graphClient.serialize(merged);
|
|
1954
1980
|
}
|
|
@@ -1956,7 +1982,9 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
|
|
|
1956
1982
|
if (shouldProject && !projectionHandled && !response?.isError && response?.content?.[0]?.text) {
|
|
1957
1983
|
try {
|
|
1958
1984
|
const parsed = JSON.parse(response.content[0].text);
|
|
1959
|
-
response.content[0].text = graphClient.serialize(
|
|
1985
|
+
response.content[0].text = graphClient.serialize(
|
|
1986
|
+
applyMemberFilter(applyProjection(parsed))
|
|
1987
|
+
);
|
|
1960
1988
|
} catch {
|
|
1961
1989
|
}
|
|
1962
1990
|
}
|
|
@@ -1969,6 +1997,14 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
|
|
|
1969
1997
|
} catch {
|
|
1970
1998
|
}
|
|
1971
1999
|
}
|
|
2000
|
+
if (willFilterMembers && !memberFilterHandled && !response?.isError && response?.content?.[0]?.text) {
|
|
2001
|
+
try {
|
|
2002
|
+
response.content[0].text = graphClient.serialize(
|
|
2003
|
+
applyMemberFilter(JSON.parse(response.content[0].text))
|
|
2004
|
+
);
|
|
2005
|
+
} catch {
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
1972
2008
|
if (response?.content?.[0]?.text) {
|
|
1973
2009
|
const responseText = response.content[0].text;
|
|
1974
2010
|
logger.info(`Response size: ${responseText.length} characters`);
|
|
@@ -2125,6 +2161,9 @@ function registerGraphTools(server, graphClient, options = {}) {
|
|
|
2125
2161
|
const maxPages = getMaxPages();
|
|
2126
2162
|
paramSchema["fetchAllPages"] = z.boolean().describe(getFetchAllPagesParamDescription(maxPages, tool.alias)).optional();
|
|
2127
2163
|
}
|
|
2164
|
+
if (MEMBER_NAME_FILTER_TOOLS.has(tool.alias)) {
|
|
2165
|
+
paramSchema["name"] = z.string().describe(MEMBER_NAME_PARAM_DESCRIPTION).optional();
|
|
2166
|
+
}
|
|
2128
2167
|
if (isSkiptokenApplicable(tool, Object.keys(paramSchema))) {
|
|
2129
2168
|
paramSchema["skiptoken"] = z.string().describe(SKIPTOKEN_PARAM_DESCRIPTION).optional();
|
|
2130
2169
|
}
|
|
@@ -2545,6 +2584,7 @@ export {
|
|
|
2545
2584
|
UTILITY_TOOLS,
|
|
2546
2585
|
buildDiscoverySearchIndex,
|
|
2547
2586
|
buildToolsRegistry,
|
|
2587
|
+
filterMembersByName,
|
|
2548
2588
|
isDestructiveOperation,
|
|
2549
2589
|
registerDiscoveryTools,
|
|
2550
2590
|
registerGraphTools,
|
package/dist/lib/log-redactor.js
CHANGED
|
@@ -22,13 +22,30 @@ const REDACTIONS = [
|
|
|
22
22
|
{
|
|
23
23
|
pattern: /([?&]code=)[^&\s"']+/gi,
|
|
24
24
|
replacement: "$1[REDACTED]"
|
|
25
|
-
},
|
|
26
|
-
// Email addresses / UPNs
|
|
27
|
-
{
|
|
28
|
-
pattern: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,
|
|
29
|
-
replacement: "[REDACTED_EMAIL]"
|
|
30
25
|
}
|
|
31
26
|
];
|
|
27
|
+
const EMAIL_LOCAL_CHAR = /[A-Za-z0-9._%+-]/;
|
|
28
|
+
const EMAIL_DOMAIN = /[A-Za-z0-9.-]+\.[A-Za-z]{2,}/y;
|
|
29
|
+
function redactEmails(input) {
|
|
30
|
+
let at = input.indexOf("@");
|
|
31
|
+
if (at === -1) return input;
|
|
32
|
+
let out = "";
|
|
33
|
+
let copied = 0;
|
|
34
|
+
while (at !== -1) {
|
|
35
|
+
let start = at;
|
|
36
|
+
while (start > copied && EMAIL_LOCAL_CHAR.test(input[start - 1])) start--;
|
|
37
|
+
EMAIL_DOMAIN.lastIndex = at + 1;
|
|
38
|
+
const domain = start < at ? EMAIL_DOMAIN.exec(input) : null;
|
|
39
|
+
if (domain) {
|
|
40
|
+
out += input.slice(copied, start) + "[REDACTED_EMAIL]";
|
|
41
|
+
copied = at + 1 + domain[0].length;
|
|
42
|
+
at = input.indexOf("@", copied);
|
|
43
|
+
} else {
|
|
44
|
+
at = input.indexOf("@", at + 1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out + input.slice(copied);
|
|
48
|
+
}
|
|
32
49
|
function redactionEnabled() {
|
|
33
50
|
const raw = process.env.MS365_MCP_REDACT_PII;
|
|
34
51
|
if (raw === void 0) return true;
|
|
@@ -39,7 +56,7 @@ function redactSensitive(input) {
|
|
|
39
56
|
for (const { pattern, replacement } of REDACTIONS) {
|
|
40
57
|
out = out.replace(pattern, replacement);
|
|
41
58
|
}
|
|
42
|
-
return out;
|
|
59
|
+
return redactEmails(out);
|
|
43
60
|
}
|
|
44
61
|
export {
|
|
45
62
|
redactSensitive,
|
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.167.0",
|
|
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
|
@@ -1737,7 +1737,7 @@
|
|
|
1737
1737
|
"toolName": "list-chat-members",
|
|
1738
1738
|
"presets": ["teams", "work"],
|
|
1739
1739
|
"workScopes": ["ChatMember.Read"],
|
|
1740
|
-
"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."
|
|
1740
|
+
"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
1741
|
},
|
|
1742
1742
|
{
|
|
1743
1743
|
"pathPattern": "/chats/{chat-id}/messages",
|
|
@@ -1958,7 +1958,8 @@
|
|
|
1958
1958
|
"method": "get",
|
|
1959
1959
|
"toolName": "list-team-members",
|
|
1960
1960
|
"presets": ["teams", "work"],
|
|
1961
|
-
"workScopes": ["TeamMember.Read.All"]
|
|
1961
|
+
"workScopes": ["TeamMember.Read.All"],
|
|
1962
|
+
"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."
|
|
1962
1963
|
},
|
|
1963
1964
|
{
|
|
1964
1965
|
"pathPattern": "/teams/{team-id}/channels/{channel-id}/filesFolder",
|