@softeria/ms-365-mcp-server 0.134.0 → 0.134.2
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 +13 -0
- package/dist/generated/hack.js +16 -4
- package/dist/graph-tools.js +41 -57
- package/dist/lib/param-descriptions.js +93 -0
- package/dist/lib/path-params.js +16 -0
- package/dist/lib/tool-schema.js +56 -4
- package/package.json +1 -1
- package/src/generated/hack.ts +23 -4
|
@@ -393,6 +393,19 @@ describe("graph-tools", () => {
|
|
|
393
393
|
expect(schema["expand"].description).toContain("navigation");
|
|
394
394
|
expect(schema["expand"].description).toContain("attachments");
|
|
395
395
|
});
|
|
396
|
+
it("should describe path params it synthesizes itself", async () => {
|
|
397
|
+
const endpoint = makeEndpoint({ path: "/me/messages/:messageId" });
|
|
398
|
+
const config = makeConfig();
|
|
399
|
+
mockEndpoints.push(endpoint);
|
|
400
|
+
mockEndpointsJson = [config];
|
|
401
|
+
const server = createMockServer();
|
|
402
|
+
const { registerGraphTools } = await loadModule();
|
|
403
|
+
registerGraphTools(server, createMockGraphClient());
|
|
404
|
+
const schema = server.tools.get("test-tool").schema;
|
|
405
|
+
expect(schema["messageId"]).toBeDefined();
|
|
406
|
+
expect(schema["messageId"].description).not.toBe("Path parameter: messageId");
|
|
407
|
+
expect(schema["messageId"].description).toContain("not as 'id'");
|
|
408
|
+
});
|
|
396
409
|
});
|
|
397
410
|
describe("MS365_MCP_MAX_TOP", () => {
|
|
398
411
|
const prevMaxTop = process.env.MS365_MCP_MAX_TOP;
|
package/dist/generated/hack.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { describePathParam } from "../lib/path-params.js";
|
|
2
3
|
function makeApi(endpoints) {
|
|
3
4
|
return endpoints;
|
|
4
5
|
}
|
|
6
|
+
function isPathParamStub(param) {
|
|
7
|
+
const description = param.description ?? param.schema?.description;
|
|
8
|
+
return typeof description === "string" && /^Path parameter: /.test(description);
|
|
9
|
+
}
|
|
5
10
|
class Zodios {
|
|
6
11
|
constructor(baseUrlOrEndpoints, endpoints, options) {
|
|
7
12
|
if (typeof baseUrlOrEndpoints === "string") {
|
|
@@ -19,17 +24,24 @@ class Zodios {
|
|
|
19
24
|
pathParams.push(match[1]);
|
|
20
25
|
}
|
|
21
26
|
for (const pathParam of pathParams) {
|
|
22
|
-
const
|
|
27
|
+
const existing = endpoint.parameters.find(
|
|
23
28
|
(param) => param.name === pathParam || param.name === pathParam.replace(/[$_]+/g, "")
|
|
24
29
|
);
|
|
25
|
-
if (!
|
|
30
|
+
if (!existing) {
|
|
31
|
+
const description = describePathParam(pathParam);
|
|
26
32
|
const newParam = {
|
|
27
33
|
name: pathParam,
|
|
28
34
|
type: "Path",
|
|
29
|
-
schema: z.string().describe(
|
|
30
|
-
description
|
|
35
|
+
schema: z.string().describe(description),
|
|
36
|
+
description
|
|
31
37
|
};
|
|
32
38
|
endpoint.parameters.push(newParam);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (existing.type === "Path" && isPathParamStub(existing)) {
|
|
42
|
+
const description = describePathParam(existing.name);
|
|
43
|
+
existing.description = description;
|
|
44
|
+
existing.schema = z.string().describe(description);
|
|
33
45
|
}
|
|
34
46
|
}
|
|
35
47
|
return endpoint;
|
package/dist/graph-tools.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "crypto";
|
|
|
2
2
|
import logger from "./logger.js";
|
|
3
3
|
import { auditLog, getUserIdentityForAudit } from "./audit-log.js";
|
|
4
4
|
import { isDestructiveOperation } from "./lib/destructive-ops.js";
|
|
5
|
+
import { describePathParam } from "./lib/path-params.js";
|
|
5
6
|
import {
|
|
6
7
|
getEndpointScopeGroups,
|
|
7
8
|
getMissingAllowedScopesForGroups,
|
|
@@ -20,15 +21,33 @@ import { getRequestTokens } from "./request-context.js";
|
|
|
20
21
|
import { parseTeamsUrl } from "./lib/teams-url-parser.js";
|
|
21
22
|
import { buildBM25Index, scoreQuery, tokenize } from "./lib/bm25.js";
|
|
22
23
|
import { describeToolSchema, describeUtilityToolSchema } from "./lib/tool-schema.js";
|
|
24
|
+
import {
|
|
25
|
+
TOP_UNSUPPORTED_DELTA_TOOLS,
|
|
26
|
+
shouldOmitTopParam,
|
|
27
|
+
paginationAllowed,
|
|
28
|
+
positiveIntFromEnv,
|
|
29
|
+
DEFAULT_MAX_PAGES,
|
|
30
|
+
getMaxPages,
|
|
31
|
+
isFetchAllPagesApplicable,
|
|
32
|
+
FILTER_PARAM_DESCRIPTION,
|
|
33
|
+
SEARCH_PARAM_DESCRIPTION,
|
|
34
|
+
SELECT_PARAM_DESCRIPTION,
|
|
35
|
+
EXPAND_PARAM_DESCRIPTION,
|
|
36
|
+
ORDERBY_PARAM_DESCRIPTION,
|
|
37
|
+
TOP_PARAM_DESCRIPTION,
|
|
38
|
+
SKIP_PARAM_DESCRIPTION,
|
|
39
|
+
COUNT_PARAM_DESCRIPTION,
|
|
40
|
+
CONFIRM_PARAM_DESCRIPTION,
|
|
41
|
+
TIMEZONE_PARAM_DESCRIPTION,
|
|
42
|
+
EXPAND_EXTENDED_PROPERTIES_PARAM_DESCRIPTION,
|
|
43
|
+
getAccountParamDescription,
|
|
44
|
+
getFetchAllPagesParamDescription
|
|
45
|
+
} from "./lib/param-descriptions.js";
|
|
23
46
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
47
|
const __dirname = path.dirname(__filename);
|
|
25
48
|
const endpointsData = JSON.parse(
|
|
26
49
|
readFileSync(path.join(__dirname, "endpoints.json"), "utf8")
|
|
27
50
|
);
|
|
28
|
-
const TOP_UNSUPPORTED_DELTA_TOOLS = /* @__PURE__ */ new Set([
|
|
29
|
-
"list-calendar-events-delta",
|
|
30
|
-
"list-calendar-view-delta"
|
|
31
|
-
]);
|
|
32
51
|
function withApiVersionPrefix(description, config) {
|
|
33
52
|
return config?.apiVersion === "beta" ? `[beta] ${description}` : description;
|
|
34
53
|
}
|
|
@@ -52,23 +71,7 @@ function clampTopQueryParam(queryParams) {
|
|
|
52
71
|
logger.info(`Clamping $top from ${requested} to ${cap} (MS365_MCP_MAX_TOP)`);
|
|
53
72
|
queryParams["$top"] = String(cap);
|
|
54
73
|
}
|
|
55
|
-
const DEFAULT_MAX_PAGES = 100;
|
|
56
74
|
const DEFAULT_MAX_ITEMS = 1e4;
|
|
57
|
-
function positiveIntFromEnv(name, defaultValue) {
|
|
58
|
-
const raw = process.env[name];
|
|
59
|
-
if (raw === void 0 || raw === "") return defaultValue;
|
|
60
|
-
const n = Number.parseInt(raw, 10);
|
|
61
|
-
if (!Number.isFinite(n) || n < 1) {
|
|
62
|
-
logger.warn(`Ignoring invalid ${name}=${JSON.stringify(raw)} (use a positive integer)`);
|
|
63
|
-
return defaultValue;
|
|
64
|
-
}
|
|
65
|
-
return n;
|
|
66
|
-
}
|
|
67
|
-
function paginationAllowed() {
|
|
68
|
-
const raw = process.env.MS365_MCP_ALLOW_PAGINATION;
|
|
69
|
-
if (raw === void 0 || raw === "") return true;
|
|
70
|
-
return !/^(0|false|no)$/i.test(raw.trim());
|
|
71
|
-
}
|
|
72
75
|
function isConfirmGateEnabled() {
|
|
73
76
|
return process.env.MS365_MCP_REQUIRE_CONFIRM === "true";
|
|
74
77
|
}
|
|
@@ -1000,81 +1003,62 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
1000
1003
|
for (const match of pathParamMatches) {
|
|
1001
1004
|
const pathParamName = match[1];
|
|
1002
1005
|
if (!(pathParamName in paramSchema)) {
|
|
1003
|
-
paramSchema[pathParamName] = z.string().describe(
|
|
1006
|
+
paramSchema[pathParamName] = z.string().describe(describePathParam(pathParamName));
|
|
1004
1007
|
}
|
|
1005
1008
|
}
|
|
1006
|
-
if (
|
|
1007
|
-
const maxPages =
|
|
1008
|
-
paramSchema["fetchAllPages"] = z.boolean().describe(
|
|
1009
|
-
`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 $filter/$search.`
|
|
1010
|
-
).optional();
|
|
1009
|
+
if (isFetchAllPagesApplicable(tool)) {
|
|
1010
|
+
const maxPages = getMaxPages();
|
|
1011
|
+
paramSchema["fetchAllPages"] = z.boolean().describe(getFetchAllPagesParamDescription(maxPages)).optional();
|
|
1011
1012
|
}
|
|
1012
1013
|
if (paramSchema["filter"] !== void 0 || paramSchema["$filter"] !== void 0) {
|
|
1013
1014
|
const key = paramSchema["$filter"] !== void 0 ? "$filter" : "filter";
|
|
1014
|
-
paramSchema[key] = z.string().describe(
|
|
1015
|
-
"OData filter expression. Add $count=true for advanced filters (flag/flagStatus, contains()). Cannot combine with $search."
|
|
1016
|
-
).optional();
|
|
1015
|
+
paramSchema[key] = z.string().describe(FILTER_PARAM_DESCRIPTION).optional();
|
|
1017
1016
|
}
|
|
1018
1017
|
if (paramSchema["search"] !== void 0 || paramSchema["$search"] !== void 0) {
|
|
1019
1018
|
const key = paramSchema["$search"] !== void 0 ? "$search" : "search";
|
|
1020
|
-
paramSchema[key] = z.string().describe(
|
|
1019
|
+
paramSchema[key] = z.string().describe(SEARCH_PARAM_DESCRIPTION).optional();
|
|
1021
1020
|
}
|
|
1022
1021
|
if (paramSchema["select"] !== void 0 || paramSchema["$select"] !== void 0) {
|
|
1023
1022
|
const key = paramSchema["$select"] !== void 0 ? "$select" : "select";
|
|
1024
|
-
paramSchema[key] = z.string().describe(
|
|
1023
|
+
paramSchema[key] = z.string().describe(SELECT_PARAM_DESCRIPTION).optional();
|
|
1025
1024
|
}
|
|
1026
1025
|
if (paramSchema["expand"] !== void 0 || paramSchema["$expand"] !== void 0) {
|
|
1027
1026
|
const key = paramSchema["$expand"] !== void 0 ? "$expand" : "expand";
|
|
1028
|
-
paramSchema[key] = z.array(z.string()).describe(
|
|
1029
|
-
'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", and an unsupported value may be ignored rather than reported. Request ordinary fields with $select instead.'
|
|
1030
|
-
).optional();
|
|
1027
|
+
paramSchema[key] = z.array(z.string()).describe(EXPAND_PARAM_DESCRIPTION).optional();
|
|
1031
1028
|
}
|
|
1032
1029
|
if (paramSchema["orderby"] !== void 0 || paramSchema["$orderby"] !== void 0) {
|
|
1033
1030
|
const key = paramSchema["$orderby"] !== void 0 ? "$orderby" : "orderby";
|
|
1034
|
-
paramSchema[key] = z.string().describe(
|
|
1031
|
+
paramSchema[key] = z.string().describe(ORDERBY_PARAM_DESCRIPTION).optional();
|
|
1035
1032
|
}
|
|
1036
|
-
if (
|
|
1033
|
+
if (shouldOmitTopParam(tool.alias)) {
|
|
1037
1034
|
delete paramSchema["top"];
|
|
1038
1035
|
delete paramSchema["$top"];
|
|
1039
1036
|
} else if (paramSchema["top"] !== void 0 || paramSchema["$top"] !== void 0) {
|
|
1040
1037
|
const key = paramSchema["$top"] !== void 0 ? "$top" : "top";
|
|
1041
|
-
paramSchema[key] = z.number().describe(
|
|
1042
|
-
"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."
|
|
1043
|
-
).optional();
|
|
1038
|
+
paramSchema[key] = z.number().describe(TOP_PARAM_DESCRIPTION).optional();
|
|
1044
1039
|
}
|
|
1045
1040
|
if (paramSchema["skip"] !== void 0 || paramSchema["$skip"] !== void 0) {
|
|
1046
1041
|
const key = paramSchema["$skip"] !== void 0 ? "$skip" : "skip";
|
|
1047
|
-
paramSchema[key] = z.number().describe(
|
|
1042
|
+
paramSchema[key] = z.number().describe(SKIP_PARAM_DESCRIPTION).optional();
|
|
1048
1043
|
}
|
|
1049
1044
|
if (paramSchema["count"] !== void 0 || paramSchema["$count"] !== void 0) {
|
|
1050
1045
|
const countKey = paramSchema["$count"] !== void 0 ? "$count" : "count";
|
|
1051
|
-
paramSchema[countKey] = z.boolean().describe(
|
|
1052
|
-
"Set true to enable advanced query mode (ConsistencyLevel: eventual). Required for complex $filter on flag/flagStatus or contains()."
|
|
1053
|
-
).optional();
|
|
1046
|
+
paramSchema[countKey] = z.boolean().describe(COUNT_PARAM_DESCRIPTION).optional();
|
|
1054
1047
|
}
|
|
1055
1048
|
if (multiAccount) {
|
|
1056
|
-
|
|
1057
|
-
paramSchema["account"] = z.string().describe(
|
|
1058
|
-
`${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.`
|
|
1059
|
-
).optional();
|
|
1049
|
+
paramSchema["account"] = z.string().describe(getAccountParamDescription(accountNames)).optional();
|
|
1060
1050
|
}
|
|
1061
1051
|
paramSchema["includeHeaders"] = z.boolean().describe("Include response headers (including ETag) in the response metadata").optional();
|
|
1062
1052
|
paramSchema["excludeResponse"] = z.boolean().describe("Exclude the full response body and only return success or failure indication").optional();
|
|
1063
1053
|
const destructive = isDestructiveOperation(tool.method, endpointConfig);
|
|
1064
1054
|
if (destructive) {
|
|
1065
|
-
paramSchema["confirm"] = z.boolean().describe(
|
|
1066
|
-
'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.'
|
|
1067
|
-
).optional();
|
|
1055
|
+
paramSchema["confirm"] = z.boolean().describe(CONFIRM_PARAM_DESCRIPTION).optional();
|
|
1068
1056
|
}
|
|
1069
1057
|
if (endpointConfig?.supportsTimezone) {
|
|
1070
|
-
paramSchema["timezone"] = z.string().describe(
|
|
1071
|
-
'IANA timezone name (e.g., "America/New_York", "Europe/London", "Asia/Tokyo") for calendar event times. If not specified, times are returned in UTC.'
|
|
1072
|
-
).optional();
|
|
1058
|
+
paramSchema["timezone"] = z.string().describe(TIMEZONE_PARAM_DESCRIPTION).optional();
|
|
1073
1059
|
}
|
|
1074
1060
|
if (endpointConfig?.supportsExpandExtendedProperties) {
|
|
1075
|
-
paramSchema["expandExtendedProperties"] = z.boolean().describe(
|
|
1076
|
-
"When true, expands singleValueExtendedProperties on each event. Use this to retrieve custom extended properties (e.g., sync metadata) stored on calendar events."
|
|
1077
|
-
).optional();
|
|
1061
|
+
paramSchema["expandExtendedProperties"] = z.boolean().describe(EXPAND_EXTENDED_PROPERTIES_PARAM_DESCRIPTION).optional();
|
|
1078
1062
|
}
|
|
1079
1063
|
let toolDescription = withApiVersionPrefix(
|
|
1080
1064
|
(endpointConfig?.descriptionOverride ?? tool.description) || `Execute ${tool.method.toUpperCase()} request to ${tool.path}`,
|
|
@@ -1373,7 +1357,7 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
|
|
|
1373
1357
|
async ({ tool_name }) => {
|
|
1374
1358
|
const entry = toolsRegistry.get(tool_name);
|
|
1375
1359
|
if (entry) {
|
|
1376
|
-
const schema = describeToolSchema(entry.tool, entry.config);
|
|
1360
|
+
const schema = describeToolSchema(entry.tool, entry.config, { multiAccount, accountNames });
|
|
1377
1361
|
return {
|
|
1378
1362
|
content: [{ type: "text", text: JSON.stringify(schema, null, 2) }]
|
|
1379
1363
|
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import logger from "../logger.js";
|
|
2
|
+
const TOP_UNSUPPORTED_DELTA_TOOLS = /* @__PURE__ */ new Set([
|
|
3
|
+
"list-calendar-events-delta",
|
|
4
|
+
"list-calendar-view-delta"
|
|
5
|
+
]);
|
|
6
|
+
function shouldOmitTopParam(toolAlias) {
|
|
7
|
+
return TOP_UNSUPPORTED_DELTA_TOOLS.has(toolAlias);
|
|
8
|
+
}
|
|
9
|
+
function paginationAllowed() {
|
|
10
|
+
const raw = process.env.MS365_MCP_ALLOW_PAGINATION;
|
|
11
|
+
if (raw === void 0 || raw === "") return true;
|
|
12
|
+
return !/^(0|false|no)$/i.test(raw.trim());
|
|
13
|
+
}
|
|
14
|
+
const DEFAULT_MAX_PAGES = 100;
|
|
15
|
+
function positiveIntFromEnv(name, defaultValue) {
|
|
16
|
+
const raw = process.env[name];
|
|
17
|
+
if (raw === void 0 || raw === "") return defaultValue;
|
|
18
|
+
const n = Number.parseInt(raw, 10);
|
|
19
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
20
|
+
logger.warn(`Ignoring invalid ${name}=${JSON.stringify(raw)} (use a positive integer)`);
|
|
21
|
+
return defaultValue;
|
|
22
|
+
}
|
|
23
|
+
return n;
|
|
24
|
+
}
|
|
25
|
+
function getMaxPages() {
|
|
26
|
+
return positiveIntFromEnv("MS365_MCP_MAX_PAGES", DEFAULT_MAX_PAGES);
|
|
27
|
+
}
|
|
28
|
+
function isFetchAllPagesApplicable(tool) {
|
|
29
|
+
return tool.method.toUpperCase() === "GET" && tool.path.includes("/") && paginationAllowed();
|
|
30
|
+
}
|
|
31
|
+
const FILTER_PARAM_DESCRIPTION = "OData filter expression. Add $count=true for advanced filters (flag/flagStatus, contains()). Cannot combine with $search.";
|
|
32
|
+
const SEARCH_PARAM_DESCRIPTION = "KQL search query \u2014 wrap value in double quotes. Cannot combine with $filter.";
|
|
33
|
+
const SELECT_PARAM_DESCRIPTION = "Comma-separated fields to return, e.g. id,subject,from,receivedDateTime";
|
|
34
|
+
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", and an unsupported value may be ignored rather than reported. Request ordinary fields with $select instead.';
|
|
35
|
+
const ORDERBY_PARAM_DESCRIPTION = "Sort expression, e.g. receivedDateTime desc";
|
|
36
|
+
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.";
|
|
37
|
+
const SKIP_PARAM_DESCRIPTION = "Items to skip for pagination. Not supported with $search.";
|
|
38
|
+
const COUNT_PARAM_DESCRIPTION = "Set true to enable advanced query mode (ConsistencyLevel: eventual). Required for complex $filter on flag/flagStatus or contains().";
|
|
39
|
+
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.';
|
|
40
|
+
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.';
|
|
41
|
+
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.";
|
|
42
|
+
function getAccountParamDescription(accountNames) {
|
|
43
|
+
const accountHint = accountNames.length > 0 ? `Known accounts: ${accountNames.join(", ")}. ` : "";
|
|
44
|
+
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.`;
|
|
45
|
+
}
|
|
46
|
+
function getFetchAllPagesParamDescription(maxPages) {
|
|
47
|
+
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 $filter/$search.`;
|
|
48
|
+
}
|
|
49
|
+
function getODataParamDescription(bareName) {
|
|
50
|
+
switch (bareName) {
|
|
51
|
+
case "filter":
|
|
52
|
+
return FILTER_PARAM_DESCRIPTION;
|
|
53
|
+
case "search":
|
|
54
|
+
return SEARCH_PARAM_DESCRIPTION;
|
|
55
|
+
case "select":
|
|
56
|
+
return SELECT_PARAM_DESCRIPTION;
|
|
57
|
+
case "expand":
|
|
58
|
+
return EXPAND_PARAM_DESCRIPTION;
|
|
59
|
+
case "orderby":
|
|
60
|
+
return ORDERBY_PARAM_DESCRIPTION;
|
|
61
|
+
case "top":
|
|
62
|
+
return TOP_PARAM_DESCRIPTION;
|
|
63
|
+
case "skip":
|
|
64
|
+
return SKIP_PARAM_DESCRIPTION;
|
|
65
|
+
case "count":
|
|
66
|
+
return COUNT_PARAM_DESCRIPTION;
|
|
67
|
+
default:
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
CONFIRM_PARAM_DESCRIPTION,
|
|
73
|
+
COUNT_PARAM_DESCRIPTION,
|
|
74
|
+
DEFAULT_MAX_PAGES,
|
|
75
|
+
EXPAND_EXTENDED_PROPERTIES_PARAM_DESCRIPTION,
|
|
76
|
+
EXPAND_PARAM_DESCRIPTION,
|
|
77
|
+
FILTER_PARAM_DESCRIPTION,
|
|
78
|
+
ORDERBY_PARAM_DESCRIPTION,
|
|
79
|
+
SEARCH_PARAM_DESCRIPTION,
|
|
80
|
+
SELECT_PARAM_DESCRIPTION,
|
|
81
|
+
SKIP_PARAM_DESCRIPTION,
|
|
82
|
+
TIMEZONE_PARAM_DESCRIPTION,
|
|
83
|
+
TOP_PARAM_DESCRIPTION,
|
|
84
|
+
TOP_UNSUPPORTED_DELTA_TOOLS,
|
|
85
|
+
getAccountParamDescription,
|
|
86
|
+
getFetchAllPagesParamDescription,
|
|
87
|
+
getMaxPages,
|
|
88
|
+
getODataParamDescription,
|
|
89
|
+
isFetchAllPagesApplicable,
|
|
90
|
+
paginationAllowed,
|
|
91
|
+
positiveIntFromEnv,
|
|
92
|
+
shouldOmitTopParam
|
|
93
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function describePathParam(pathParamName) {
|
|
2
|
+
const match = /^(.*?)Id(\d*)$/.exec(pathParamName);
|
|
3
|
+
if (match && match[1]) {
|
|
4
|
+
const base = `Value for the '${pathParamName}' path segment. Pass it under the name '${pathParamName}', not as 'id'. `;
|
|
5
|
+
if (match[2]) {
|
|
6
|
+
return base + `It identifies a different resource from the similarly named parameter without the number; check the tool's path to see which.`;
|
|
7
|
+
}
|
|
8
|
+
const resource = match[1].replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
|
|
9
|
+
const noun = /(^|\s)object$/.test(resource) ? resource : `${resource} object`;
|
|
10
|
+
return base + `Use the 'id' field of the ${noun} as returned by Microsoft Graph.`;
|
|
11
|
+
}
|
|
12
|
+
return `Value for the '${pathParamName}' path segment.`;
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
describePathParam
|
|
16
|
+
};
|
package/dist/lib/tool-schema.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
2
|
import { isDestructiveOperation } from "./destructive-ops.js";
|
|
3
|
+
import {
|
|
4
|
+
getODataParamDescription,
|
|
5
|
+
shouldOmitTopParam,
|
|
6
|
+
isFetchAllPagesApplicable,
|
|
7
|
+
getMaxPages,
|
|
8
|
+
getFetchAllPagesParamDescription,
|
|
9
|
+
getAccountParamDescription,
|
|
10
|
+
CONFIRM_PARAM_DESCRIPTION,
|
|
11
|
+
TIMEZONE_PARAM_DESCRIPTION,
|
|
12
|
+
EXPAND_EXTENDED_PROPERTIES_PARAM_DESCRIPTION
|
|
13
|
+
} from "./param-descriptions.js";
|
|
3
14
|
function unwrapOptional(schema) {
|
|
4
15
|
const def = schema._def;
|
|
5
16
|
const typeName = def?.typeName;
|
|
@@ -8,17 +19,22 @@ function unwrapOptional(schema) {
|
|
|
8
19
|
}
|
|
9
20
|
return { inner: schema, optional: false };
|
|
10
21
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
22
|
+
function bareParamName(name) {
|
|
23
|
+
return name.startsWith("$") ? name.slice(1) : name;
|
|
24
|
+
}
|
|
25
|
+
function describeToolSchema(tool, config, ctx = {}) {
|
|
26
|
+
const omitTop = shouldOmitTopParam(tool.alias);
|
|
27
|
+
const params = (tool.parameters ?? []).filter((p) => !(omitTop && bareParamName(p.name) === "top")).map((p) => {
|
|
13
28
|
const { inner, optional } = unwrapOptional(p.schema);
|
|
14
29
|
const isPath = p.type === "Path";
|
|
15
30
|
const jsonSchema = zodToJsonSchema(inner, { target: "jsonSchema7", $refStrategy: "none" });
|
|
16
31
|
const { $schema: _s, ...schema } = jsonSchema;
|
|
32
|
+
const override = p.type === "Query" ? getODataParamDescription(bareParamName(p.name)) : void 0;
|
|
17
33
|
return {
|
|
18
34
|
name: p.name,
|
|
19
35
|
in: p.type,
|
|
20
36
|
required: isPath || !optional,
|
|
21
|
-
description: p.description,
|
|
37
|
+
description: override ?? p.description,
|
|
22
38
|
schema
|
|
23
39
|
};
|
|
24
40
|
});
|
|
@@ -27,7 +43,43 @@ function describeToolSchema(tool, config) {
|
|
|
27
43
|
name: "confirm",
|
|
28
44
|
in: "Query",
|
|
29
45
|
required: false,
|
|
30
|
-
description:
|
|
46
|
+
description: CONFIRM_PARAM_DESCRIPTION,
|
|
47
|
+
schema: { type: "boolean" }
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (isFetchAllPagesApplicable({ method: tool.method, path: tool.path })) {
|
|
51
|
+
params.push({
|
|
52
|
+
name: "fetchAllPages",
|
|
53
|
+
in: "Query",
|
|
54
|
+
required: false,
|
|
55
|
+
description: getFetchAllPagesParamDescription(getMaxPages()),
|
|
56
|
+
schema: { type: "boolean" }
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (ctx.multiAccount) {
|
|
60
|
+
params.push({
|
|
61
|
+
name: "account",
|
|
62
|
+
in: "Query",
|
|
63
|
+
required: false,
|
|
64
|
+
description: getAccountParamDescription(ctx.accountNames ?? []),
|
|
65
|
+
schema: { type: "string" }
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (config?.supportsTimezone) {
|
|
69
|
+
params.push({
|
|
70
|
+
name: "timezone",
|
|
71
|
+
in: "Query",
|
|
72
|
+
required: false,
|
|
73
|
+
description: TIMEZONE_PARAM_DESCRIPTION,
|
|
74
|
+
schema: { type: "string" }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (config?.supportsExpandExtendedProperties) {
|
|
78
|
+
params.push({
|
|
79
|
+
name: "expandExtendedProperties",
|
|
80
|
+
in: "Query",
|
|
81
|
+
required: false,
|
|
82
|
+
description: EXPAND_EXTENDED_PROPERTIES_PARAM_DESCRIPTION,
|
|
31
83
|
schema: { type: "boolean" }
|
|
32
84
|
});
|
|
33
85
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.134.
|
|
3
|
+
"version": "0.134.2",
|
|
4
4
|
"description": " A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/src/generated/hack.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { Endpoint, Parameter } from './endpoint-types.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { describePathParam } from '../lib/path-params.js';
|
|
3
4
|
|
|
4
5
|
export function makeApi(endpoints: Endpoint[]) {
|
|
5
6
|
return endpoints;
|
|
6
7
|
}
|
|
7
8
|
|
|
9
|
+
/** True when a path parameter carries only the generated `Path parameter: x` placeholder. */
|
|
10
|
+
function isPathParamStub(param: Parameter): boolean {
|
|
11
|
+
const description = param.description ?? param.schema?.description;
|
|
12
|
+
return typeof description === 'string' && /^Path parameter: /.test(description);
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
export class Zodios {
|
|
9
16
|
endpoints: Endpoint[];
|
|
10
17
|
|
|
@@ -26,18 +33,30 @@ export class Zodios {
|
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
for (const pathParam of pathParams) {
|
|
29
|
-
const
|
|
36
|
+
const existing = endpoint.parameters.find(
|
|
30
37
|
(param) => param.name === pathParam || param.name === pathParam.replace(/[$_]+/g, '')
|
|
31
38
|
);
|
|
32
39
|
|
|
33
|
-
if (!
|
|
40
|
+
if (!existing) {
|
|
41
|
+
const description = describePathParam(pathParam);
|
|
34
42
|
const newParam: Parameter = {
|
|
35
43
|
name: pathParam,
|
|
36
44
|
type: 'Path',
|
|
37
|
-
schema: z.string().describe(
|
|
38
|
-
description
|
|
45
|
+
schema: z.string().describe(description),
|
|
46
|
+
description,
|
|
39
47
|
};
|
|
40
48
|
endpoint.parameters.push(newParam);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Some operations arrive with the path parameter already declared, but described
|
|
53
|
+
// only as `Path parameter: drive-id` — no more use to the model than nothing, and
|
|
54
|
+
// spelled in kebab-case so it does not even match the name callers must send.
|
|
55
|
+
// Upgrade those; any real upstream description is left alone.
|
|
56
|
+
if (existing.type === 'Path' && isPathParamStub(existing)) {
|
|
57
|
+
const description = describePathParam(existing.name);
|
|
58
|
+
existing.description = description;
|
|
59
|
+
existing.schema = z.string().describe(description);
|
|
41
60
|
}
|
|
42
61
|
}
|
|
43
62
|
|