@softeria/ms-365-mcp-server 0.135.0 → 0.137.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/.env.example +13 -5
- package/README.md +5 -0
- package/dist/__tests__/graph-tools.test.js +232 -0
- package/dist/audit-target-resource.js +75 -0
- package/dist/generated/client.js +43 -30
- package/dist/graph-tools.js +9 -1
- package/dist/index.js +2 -1
- package/dist/load-env.js +41 -0
- package/dist/token-cache-storage.js +3 -0
- package/docs/deployment.md +1 -1
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Microsoft 365 OAuth Configuration
|
|
2
2
|
# Create an Azure AD app registration and get these values:
|
|
3
|
+
#
|
|
4
|
+
# NOTE: .env is read from the directory the server is started in, which the MCP
|
|
5
|
+
# client picks. Only the four variables below are read from it. Everything else
|
|
6
|
+
# (token cache paths, the auth-cache command, Key Vault, logging, HTTP settings)
|
|
7
|
+
# must be set in your shell or MCP client config.
|
|
3
8
|
|
|
4
9
|
# Your Azure AD App Registration Client ID
|
|
5
10
|
MS365_MCP_CLIENT_ID=your-azure-ad-app-client-id-here
|
|
@@ -38,7 +43,8 @@ MS365_MCP_TENANT_ID=common
|
|
|
38
43
|
# When set, secrets are fetched from Azure Key Vault instead of environment variables.
|
|
39
44
|
# This is useful for production deployments, especially with Azure Container Apps.
|
|
40
45
|
#
|
|
41
|
-
# MS365_MCP_KEYVAULT_URL
|
|
46
|
+
# Not read from .env - export MS365_MCP_KEYVAULT_URL in the environment instead:
|
|
47
|
+
# export MS365_MCP_KEYVAULT_URL=https://your-keyvault-name.vault.azure.net
|
|
42
48
|
#
|
|
43
49
|
# Key Vault secret names (store these in your Key Vault):
|
|
44
50
|
# - ms365-mcp-client-id (required)
|
|
@@ -58,8 +64,10 @@ MS365_MCP_TENANT_ID=common
|
|
|
58
64
|
# -------------------------------------------------------------------
|
|
59
65
|
# Headless local-MSAL deployments can store token-cache and selected-account
|
|
60
66
|
# metadata with a provider-neutral executable wrapper. The value is a real
|
|
61
|
-
# executable path, not a shell command string; put any
|
|
62
|
-
# inside the wrapper script.
|
|
67
|
+
# executable path (absolute), not a shell command string; put any
|
|
68
|
+
# provider-specific args inside the wrapper script.
|
|
63
69
|
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
70
|
+
# This one starts a process, so it is deliberately NOT read from .env. Export it
|
|
71
|
+
# in the environment that launches the server:
|
|
72
|
+
# export MS365_MCP_AUTH_CACHE_COMMAND=/path/to/ms365-auth-cache-store
|
|
73
|
+
# export MS365_MCP_AUTH_CACHE_COMMAND_TIMEOUT_MS=10000
|
package/README.md
CHANGED
|
@@ -440,6 +440,11 @@ registration:
|
|
|
440
440
|
|
|
441
441
|
With these configured, the server will use your custom Azure app instead of the built-in one.
|
|
442
442
|
|
|
443
|
+
> **Note**: `.env` is read from the directory the server is started in, and the MCP client decides what
|
|
444
|
+
> that is. Only `MS365_MCP_CLIENT_ID`, `MS365_MCP_CLIENT_SECRET`, `MS365_MCP_TENANT_ID` and
|
|
445
|
+
> `MS365_MCP_CLOUD_TYPE` are read from it. Every other variable listed above must be set in your shell
|
|
446
|
+
> or MCP client config; anything else found in a `.env` is ignored with a warning on stderr.
|
|
447
|
+
|
|
443
448
|
#### 3. Bring Your Own Token (BYOT)
|
|
444
449
|
|
|
445
450
|
If you are running ms-365-mcp-server as part of a larger system that manages Microsoft OAuth tokens externally, you can
|
|
@@ -89,6 +89,10 @@ async function loadModule() {
|
|
|
89
89
|
const mod = await import("../graph-tools.js");
|
|
90
90
|
return mod;
|
|
91
91
|
}
|
|
92
|
+
async function spyOnAuditLogger() {
|
|
93
|
+
const { __testing } = await import("../audit-log.js");
|
|
94
|
+
return vi.spyOn(__testing.auditLogger, "info").mockImplementation(() => __testing.auditLogger);
|
|
95
|
+
}
|
|
92
96
|
function createMockServer() {
|
|
93
97
|
const tools = /* @__PURE__ */ new Map();
|
|
94
98
|
return {
|
|
@@ -139,6 +143,234 @@ describe("graph-tools", () => {
|
|
|
139
143
|
expect(url).toContain("$count=true");
|
|
140
144
|
});
|
|
141
145
|
});
|
|
146
|
+
describe("audit target resources", () => {
|
|
147
|
+
it("adds target_resource to generated Graph tool audit events", async () => {
|
|
148
|
+
const endpoint = makeEndpoint({
|
|
149
|
+
alias: "get-drive-item",
|
|
150
|
+
path: "/drives/:driveId/items/:driveItemId",
|
|
151
|
+
parameters: [
|
|
152
|
+
{ name: "driveId", type: "Path", schema: z.string() },
|
|
153
|
+
{ name: "driveItemId", type: "Path", schema: z.string() }
|
|
154
|
+
]
|
|
155
|
+
});
|
|
156
|
+
const config = makeConfig({
|
|
157
|
+
toolName: "get-drive-item",
|
|
158
|
+
pathPattern: "/drives/{drive-id}/items/{driveItem-id}",
|
|
159
|
+
scopes: ["Files.Read"]
|
|
160
|
+
});
|
|
161
|
+
mockEndpoints.push(endpoint);
|
|
162
|
+
mockEndpointsJson = [config];
|
|
163
|
+
const graphClient = createMockGraphClient([
|
|
164
|
+
{ content: [{ type: "text", text: JSON.stringify({ id: "item-2" }) }] }
|
|
165
|
+
]);
|
|
166
|
+
const server = createMockServer();
|
|
167
|
+
const { registerGraphTools } = await loadModule();
|
|
168
|
+
registerGraphTools(server, graphClient);
|
|
169
|
+
const auditSpy = await spyOnAuditLogger();
|
|
170
|
+
await server.tools.get("get-drive-item").handler({
|
|
171
|
+
driveId: "drive-1",
|
|
172
|
+
driveItemId: "item-2"
|
|
173
|
+
});
|
|
174
|
+
expect(auditSpy).toHaveBeenCalledWith(
|
|
175
|
+
expect.objectContaining({
|
|
176
|
+
event: "tool.call",
|
|
177
|
+
tool: "get-drive-item",
|
|
178
|
+
status: "success",
|
|
179
|
+
target_resource: {
|
|
180
|
+
type: "drive_item",
|
|
181
|
+
id: "/drives/drive-1/items/item-2"
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
);
|
|
185
|
+
auditSpy.mockRestore();
|
|
186
|
+
});
|
|
187
|
+
it("adds target_resource to failed generated Graph tool audit events", async () => {
|
|
188
|
+
const endpoint = makeEndpoint({
|
|
189
|
+
alias: "get-drive-item",
|
|
190
|
+
path: "/drives/:driveId/items/:driveItemId",
|
|
191
|
+
parameters: [
|
|
192
|
+
{ name: "driveId", type: "Path", schema: z.string() },
|
|
193
|
+
{ name: "driveItemId", type: "Path", schema: z.string() }
|
|
194
|
+
]
|
|
195
|
+
});
|
|
196
|
+
const config = makeConfig({
|
|
197
|
+
toolName: "get-drive-item",
|
|
198
|
+
pathPattern: "/drives/{drive-id}/items/{driveItem-id}",
|
|
199
|
+
scopes: ["Files.Read"]
|
|
200
|
+
});
|
|
201
|
+
mockEndpoints.push(endpoint);
|
|
202
|
+
mockEndpointsJson = [config];
|
|
203
|
+
const graphClient = createMockGraphClient();
|
|
204
|
+
graphClient.graphRequest.mockRejectedValueOnce(
|
|
205
|
+
Object.assign(new Error("Forbidden"), { status: 403 })
|
|
206
|
+
);
|
|
207
|
+
const server = createMockServer();
|
|
208
|
+
const { registerGraphTools } = await loadModule();
|
|
209
|
+
registerGraphTools(
|
|
210
|
+
server,
|
|
211
|
+
graphClient
|
|
212
|
+
);
|
|
213
|
+
const auditSpy = await spyOnAuditLogger();
|
|
214
|
+
const result = await server.tools.get("get-drive-item").handler({
|
|
215
|
+
driveId: "drive-1",
|
|
216
|
+
driveItemId: "item-2"
|
|
217
|
+
});
|
|
218
|
+
expect(result.isError).toBe(true);
|
|
219
|
+
expect(auditSpy).toHaveBeenCalledWith(
|
|
220
|
+
expect.objectContaining({
|
|
221
|
+
event: "tool.call",
|
|
222
|
+
tool: "get-drive-item",
|
|
223
|
+
status: "error",
|
|
224
|
+
error_code: 403,
|
|
225
|
+
target_resource: {
|
|
226
|
+
type: "drive_item",
|
|
227
|
+
id: "/drives/drive-1/items/item-2"
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
auditSpy.mockRestore();
|
|
232
|
+
});
|
|
233
|
+
it("derives target_resource from generic ID path parameters", async () => {
|
|
234
|
+
const endpoint = makeEndpoint({
|
|
235
|
+
alias: "get-mail-message",
|
|
236
|
+
path: "/me/messages/:messageId",
|
|
237
|
+
parameters: [{ name: "messageId", type: "Path", schema: z.string() }]
|
|
238
|
+
});
|
|
239
|
+
const config = makeConfig({
|
|
240
|
+
toolName: "get-mail-message",
|
|
241
|
+
pathPattern: "/me/messages/{message-id}"
|
|
242
|
+
});
|
|
243
|
+
mockEndpoints.push(endpoint);
|
|
244
|
+
mockEndpointsJson = [config];
|
|
245
|
+
const graphClient = createMockGraphClient([
|
|
246
|
+
{ content: [{ type: "text", text: JSON.stringify({ id: "message-1" }) }] }
|
|
247
|
+
]);
|
|
248
|
+
const server = createMockServer();
|
|
249
|
+
const { registerGraphTools } = await loadModule();
|
|
250
|
+
registerGraphTools(server, graphClient);
|
|
251
|
+
const auditSpy = await spyOnAuditLogger();
|
|
252
|
+
await server.tools.get("get-mail-message").handler({
|
|
253
|
+
messageId: "message-1"
|
|
254
|
+
});
|
|
255
|
+
expect(auditSpy).toHaveBeenCalledWith(
|
|
256
|
+
expect.objectContaining({
|
|
257
|
+
event: "tool.call",
|
|
258
|
+
tool: "get-mail-message",
|
|
259
|
+
status: "success",
|
|
260
|
+
target_resource: {
|
|
261
|
+
type: "message",
|
|
262
|
+
id: "/me/messages/message-1"
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
);
|
|
266
|
+
auditSpy.mockRestore();
|
|
267
|
+
});
|
|
268
|
+
it("omits target_resource when an ID path parameter is missing", async () => {
|
|
269
|
+
const endpoint = makeEndpoint({
|
|
270
|
+
alias: "get-drive-item",
|
|
271
|
+
path: "/drives/:driveId/items/:driveItemId",
|
|
272
|
+
parameters: [
|
|
273
|
+
{ name: "driveId", type: "Path", schema: z.string() },
|
|
274
|
+
{ name: "driveItemId", type: "Path", schema: z.string() }
|
|
275
|
+
]
|
|
276
|
+
});
|
|
277
|
+
const config = makeConfig({
|
|
278
|
+
toolName: "get-drive-item",
|
|
279
|
+
pathPattern: "/drives/{drive-id}/items/{driveItem-id}",
|
|
280
|
+
scopes: ["Files.Read"]
|
|
281
|
+
});
|
|
282
|
+
mockEndpoints.push(endpoint);
|
|
283
|
+
mockEndpointsJson = [config];
|
|
284
|
+
const graphClient = createMockGraphClient([
|
|
285
|
+
{ content: [{ type: "text", text: JSON.stringify({ id: "item-2" }) }] }
|
|
286
|
+
]);
|
|
287
|
+
const server = createMockServer();
|
|
288
|
+
const { registerGraphTools } = await loadModule();
|
|
289
|
+
registerGraphTools(server, graphClient);
|
|
290
|
+
const auditSpy = await spyOnAuditLogger();
|
|
291
|
+
await server.tools.get("get-drive-item").handler({
|
|
292
|
+
driveId: "drive-1"
|
|
293
|
+
});
|
|
294
|
+
const [payload] = auditSpy.mock.calls[0];
|
|
295
|
+
expect(payload).toMatchObject({
|
|
296
|
+
event: "tool.call",
|
|
297
|
+
tool: "get-drive-item",
|
|
298
|
+
status: "success"
|
|
299
|
+
});
|
|
300
|
+
expect(payload).not.toHaveProperty("target_resource");
|
|
301
|
+
auditSpy.mockRestore();
|
|
302
|
+
});
|
|
303
|
+
it("omits SharePoint path parameters from target_resource", async () => {
|
|
304
|
+
const endpoint = makeEndpoint({
|
|
305
|
+
alias: "get-sharepoint-site-by-path",
|
|
306
|
+
path: "/sites/:siteId/getByPath(path=':path')",
|
|
307
|
+
parameters: [
|
|
308
|
+
{ name: "siteId", type: "Path", schema: z.string() },
|
|
309
|
+
{ name: "path", type: "Path", schema: z.string() }
|
|
310
|
+
]
|
|
311
|
+
});
|
|
312
|
+
const config = makeConfig({
|
|
313
|
+
toolName: "get-sharepoint-site-by-path",
|
|
314
|
+
pathPattern: "/sites/{site-id}:/{path}",
|
|
315
|
+
scopes: [["Sites.Read.All"], ["Sites.Selected"]]
|
|
316
|
+
});
|
|
317
|
+
mockEndpoints.push(endpoint);
|
|
318
|
+
mockEndpointsJson = [config];
|
|
319
|
+
const graphClient = createMockGraphClient([
|
|
320
|
+
{ content: [{ type: "text", text: JSON.stringify({ id: "site-1" }) }] }
|
|
321
|
+
]);
|
|
322
|
+
const server = createMockServer();
|
|
323
|
+
const { registerGraphTools } = await loadModule();
|
|
324
|
+
registerGraphTools(server, graphClient);
|
|
325
|
+
const auditSpy = await spyOnAuditLogger();
|
|
326
|
+
await server.tools.get("get-sharepoint-site-by-path").handler({
|
|
327
|
+
siteId: "contoso.sharepoint.com",
|
|
328
|
+
path: "/sites/Finance"
|
|
329
|
+
});
|
|
330
|
+
expect(auditSpy).toHaveBeenCalledWith(
|
|
331
|
+
expect.objectContaining({
|
|
332
|
+
event: "tool.call",
|
|
333
|
+
tool: "get-sharepoint-site-by-path",
|
|
334
|
+
status: "success",
|
|
335
|
+
target_resource: {
|
|
336
|
+
type: "site",
|
|
337
|
+
id: "/sites/contoso.sharepoint.com"
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
);
|
|
341
|
+
const [payload] = auditSpy.mock.calls[0];
|
|
342
|
+
expect(JSON.stringify(payload)).not.toContain("Finance");
|
|
343
|
+
auditSpy.mockRestore();
|
|
344
|
+
});
|
|
345
|
+
it("omits target_resource for generated broad list/search audit events", async () => {
|
|
346
|
+
const endpoint = makeEndpoint({
|
|
347
|
+
alias: "list-mail-messages",
|
|
348
|
+
path: "/me/messages"
|
|
349
|
+
});
|
|
350
|
+
const config = makeConfig({
|
|
351
|
+
toolName: "list-mail-messages",
|
|
352
|
+
pathPattern: "/me/messages"
|
|
353
|
+
});
|
|
354
|
+
mockEndpoints.push(endpoint);
|
|
355
|
+
mockEndpointsJson = [config];
|
|
356
|
+
const graphClient = createMockGraphClient([
|
|
357
|
+
{ content: [{ type: "text", text: JSON.stringify({ value: [] }) }] }
|
|
358
|
+
]);
|
|
359
|
+
const server = createMockServer();
|
|
360
|
+
const { registerGraphTools } = await loadModule();
|
|
361
|
+
registerGraphTools(server, graphClient);
|
|
362
|
+
const auditSpy = await spyOnAuditLogger();
|
|
363
|
+
await server.tools.get("list-mail-messages").handler({ search: "budget" });
|
|
364
|
+
const [payload] = auditSpy.mock.calls[0];
|
|
365
|
+
expect(payload).toMatchObject({
|
|
366
|
+
event: "tool.call",
|
|
367
|
+
tool: "list-mail-messages",
|
|
368
|
+
status: "success"
|
|
369
|
+
});
|
|
370
|
+
expect(payload).not.toHaveProperty("target_resource");
|
|
371
|
+
auditSpy.mockRestore();
|
|
372
|
+
});
|
|
373
|
+
});
|
|
142
374
|
describe("fetchAllPages pagination", () => {
|
|
143
375
|
it("should follow @odata.nextLink and combine results", async () => {
|
|
144
376
|
const endpoint = makeEndpoint();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const CONTROL_PARAM_NAMES = /* @__PURE__ */ new Set([
|
|
2
|
+
"account",
|
|
3
|
+
"confirm",
|
|
4
|
+
"fetchAllPages",
|
|
5
|
+
"includeHeaders",
|
|
6
|
+
"excludeResponse",
|
|
7
|
+
"timezone",
|
|
8
|
+
"expandExtendedProperties"
|
|
9
|
+
]);
|
|
10
|
+
function toCamelCase(name) {
|
|
11
|
+
return name.replace(/-([a-zA-Z])/g, (_, c) => c.toUpperCase());
|
|
12
|
+
}
|
|
13
|
+
function toKebabCase(name) {
|
|
14
|
+
return name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
15
|
+
}
|
|
16
|
+
function toSnakeCase(name) {
|
|
17
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
function valueForPlaceholder(placeholderName, params) {
|
|
20
|
+
const candidates = [placeholderName, toCamelCase(placeholderName), toKebabCase(placeholderName)];
|
|
21
|
+
for (const candidate of candidates) {
|
|
22
|
+
if (CONTROL_PARAM_NAMES.has(candidate)) continue;
|
|
23
|
+
const value = params[candidate];
|
|
24
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
25
|
+
return encodeURIComponent(String(value)).replace(/%3D/g, "=");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
function resolveGraphPathForAudit(pathPattern, params = {}) {
|
|
31
|
+
if (!pathPattern) return void 0;
|
|
32
|
+
let resolvedPath = pathPattern;
|
|
33
|
+
resolvedPath = resolvedPath.replace(/:([A-Za-z][A-Za-z0-9-]*)/g, (match, name) => {
|
|
34
|
+
return valueForPlaceholder(name, params) ?? match;
|
|
35
|
+
});
|
|
36
|
+
resolvedPath = resolvedPath.replace(/\{([^}]+)\}/g, (match, name) => {
|
|
37
|
+
return valueForPlaceholder(name, params) ?? match;
|
|
38
|
+
});
|
|
39
|
+
if (/:([A-Za-z][A-Za-z0-9-]*)|\{[^}]+\}/.test(resolvedPath)) {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
return resolvedPath.startsWith("/") ? resolvedPath : `/${resolvedPath}`;
|
|
43
|
+
}
|
|
44
|
+
function idPlaceholderBase(name) {
|
|
45
|
+
const base = name.replace(/[-_]?id\d*$/i, "");
|
|
46
|
+
if (base === name || base.length === 0) return void 0;
|
|
47
|
+
return base;
|
|
48
|
+
}
|
|
49
|
+
function deriveTargetResource(input) {
|
|
50
|
+
const pathPattern = input.pathPattern;
|
|
51
|
+
if (!pathPattern) return void 0;
|
|
52
|
+
const placeholderPattern = /\{([^}]+)\}|:([A-Za-z][A-Za-z0-9-]*)/g;
|
|
53
|
+
let lastTarget;
|
|
54
|
+
for (const match of pathPattern.matchAll(placeholderPattern)) {
|
|
55
|
+
const name = match[1] ?? match[2];
|
|
56
|
+
const base = idPlaceholderBase(name);
|
|
57
|
+
if (!base) continue;
|
|
58
|
+
lastTarget = {
|
|
59
|
+
base,
|
|
60
|
+
end: match.index + match[0].length
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (!lastTarget) return void 0;
|
|
64
|
+
const targetPattern = pathPattern.slice(0, lastTarget.end);
|
|
65
|
+
const id = resolveGraphPathForAudit(targetPattern, input.params ?? {});
|
|
66
|
+
if (!id) return void 0;
|
|
67
|
+
return {
|
|
68
|
+
type: toSnakeCase(lastTarget.base),
|
|
69
|
+
id
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
deriveTargetResource,
|
|
74
|
+
resolveGraphPathForAudit
|
|
75
|
+
};
|
package/dist/generated/client.js
CHANGED
|
@@ -844,8 +844,8 @@ const microsoft_graph_driveItem = z.object({
|
|
|
844
844
|
webUrl: z.string().describe(
|
|
845
845
|
"URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
|
|
846
846
|
).nullish(),
|
|
847
|
-
createdByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
848
|
-
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
847
|
+
createdByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
848
|
+
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
849
849
|
audio: microsoft_graph_audio.optional(),
|
|
850
850
|
bundle: microsoft_graph_bundle.optional(),
|
|
851
851
|
cTag: z.string().describe(
|
|
@@ -2584,8 +2584,8 @@ const microsoft_graph_listItem = z.object({
|
|
|
2584
2584
|
webUrl: z.string().describe(
|
|
2585
2585
|
"URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
|
|
2586
2586
|
).nullish(),
|
|
2587
|
-
createdByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
2588
|
-
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
2587
|
+
createdByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
2588
|
+
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
2589
2589
|
contentType: microsoft_graph_contentTypeInfo.optional(),
|
|
2590
2590
|
deleted: microsoft_graph_deleted.optional(),
|
|
2591
2591
|
sharepointIds: microsoft_graph_sharepointIds.optional(),
|
|
@@ -2593,7 +2593,7 @@ const microsoft_graph_listItem = z.object({
|
|
|
2593
2593
|
documentSetVersions: z.array(microsoft_graph_documentSetVersion).describe("Version information for a document set version created by a user.").optional(),
|
|
2594
2594
|
driveItem: microsoft_graph_driveItem.describe("[Note: Simplified from 45 properties to 25 most common ones]").optional(),
|
|
2595
2595
|
fields: microsoft_graph_fieldValueSet.optional(),
|
|
2596
|
-
permissions: z.array(microsoft_graph_permission).optional(),
|
|
2596
|
+
permissions: z.array(microsoft_graph_permission).describe("The set of permissions for the item. Read-only. Nullable.").optional(),
|
|
2597
2597
|
versions: z.array(microsoft_graph_listItemVersion).describe("The list of previous versions of the list item.").optional()
|
|
2598
2598
|
}).passthrough();
|
|
2599
2599
|
const microsoft_graph_longRunningOperationStatus = z.enum([
|
|
@@ -2706,8 +2706,8 @@ const microsoft_graph_list = z.lazy(
|
|
|
2706
2706
|
webUrl: z.string().describe(
|
|
2707
2707
|
"URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
|
|
2708
2708
|
).nullish(),
|
|
2709
|
-
createdByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
2710
|
-
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
2709
|
+
createdByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
2710
|
+
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
2711
2711
|
displayName: z.string().describe("The displayable title of the list.").nullish(),
|
|
2712
2712
|
list: microsoft_graph_listInfo.optional(),
|
|
2713
2713
|
sharepointIds: microsoft_graph_sharepointIds.optional(),
|
|
@@ -2717,7 +2717,7 @@ const microsoft_graph_list = z.lazy(
|
|
|
2717
2717
|
drive: microsoft_graph_drive.optional(),
|
|
2718
2718
|
items: z.array(microsoft_graph_listItem).describe("All items contained in the list.").optional(),
|
|
2719
2719
|
operations: z.array(microsoft_graph_richLongRunningOperation).describe("The collection of long-running operations on the list.").optional(),
|
|
2720
|
-
permissions: z.array(microsoft_graph_permission).optional(),
|
|
2720
|
+
permissions: z.array(microsoft_graph_permission).describe("The set of permissions for the item. Read-only. Nullable.").optional(),
|
|
2721
2721
|
subscriptions: z.array(microsoft_graph_subscription).describe("The set of subscriptions on the list.").optional()
|
|
2722
2722
|
}).passthrough()
|
|
2723
2723
|
);
|
|
@@ -2739,8 +2739,8 @@ const microsoft_graph_drive = z.lazy(
|
|
|
2739
2739
|
webUrl: z.string().describe(
|
|
2740
2740
|
"URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
|
|
2741
2741
|
).nullish(),
|
|
2742
|
-
createdByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
2743
|
-
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
2742
|
+
createdByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
2743
|
+
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
2744
2744
|
driveType: z.string().describe(
|
|
2745
2745
|
"Describes the type of drive represented by this resource. OneDrive personal drives return personal. OneDrive for Business returns business. SharePoint document libraries return documentLibrary. Read-only."
|
|
2746
2746
|
).nullish(),
|
|
@@ -3014,7 +3014,7 @@ const microsoft_graph_team = z.lazy(
|
|
|
3014
3014
|
).nullish(),
|
|
3015
3015
|
allChannels: z.array(microsoft_graph_channel).describe("List of channels either hosted in or shared with the team (incoming channels).").optional(),
|
|
3016
3016
|
channels: z.array(microsoft_graph_channel).describe("The collection of channels and messages associated with the team.").optional(),
|
|
3017
|
-
group: microsoft_graph_group.describe("[Note: Simplified from
|
|
3017
|
+
group: microsoft_graph_group.describe("[Note: Simplified from 81 properties to 25 most common ones]").optional(),
|
|
3018
3018
|
incomingChannels: z.array(microsoft_graph_channel).describe("List of channels shared with the team.").optional(),
|
|
3019
3019
|
installedApps: z.array(microsoft_graph_teamsAppInstallation).describe("The apps installed in this team.").optional(),
|
|
3020
3020
|
members: z.array(microsoft_graph_conversationMember).describe("Members and owners of the team.").optional(),
|
|
@@ -3586,6 +3586,13 @@ const microsoft_graph_chatInfo = z.object({
|
|
|
3586
3586
|
const microsoft_graph_chatRestrictions = z.object({
|
|
3587
3587
|
allowTextOnly: z.boolean().describe("Indicates whether only text is allowed in the meeting chat. Optional.").nullish()
|
|
3588
3588
|
}).passthrough();
|
|
3589
|
+
const microsoft_graph_cloudVideoInteropInfo = z.object({
|
|
3590
|
+
moreInfoWebUrl: z.string().describe("Provides other video teleconferencing (VTC) dial-in options. Read-only.").nullish(),
|
|
3591
|
+
tenantKey: z.string().describe(
|
|
3592
|
+
"The tenant key that is used to dial into the interactive voice response (IVR) of the partner CVI service."
|
|
3593
|
+
).nullish(),
|
|
3594
|
+
videoTeleconferenceId: z.string().describe("The video teleconferencing ID. Read-only.").nullish()
|
|
3595
|
+
}).passthrough();
|
|
3589
3596
|
const microsoft_graph_joinMeetingIdSettings = z.object({
|
|
3590
3597
|
isPasscodeRequired: z.boolean().describe(
|
|
3591
3598
|
"Indicates whether a passcode is required to join a meeting when using joinMeetingId. Optional."
|
|
@@ -3617,6 +3624,7 @@ const microsoft_graph_onlineMeeting = z.object({
|
|
|
3617
3624
|
audioConferencing: microsoft_graph_audioConferencing.optional(),
|
|
3618
3625
|
chatInfo: microsoft_graph_chatInfo.optional(),
|
|
3619
3626
|
chatRestrictions: microsoft_graph_chatRestrictions.optional(),
|
|
3627
|
+
cloudVideoInteropInfo: microsoft_graph_cloudVideoInteropInfo.optional(),
|
|
3620
3628
|
expiryDateTime: z.string().regex(
|
|
3621
3629
|
/^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$/
|
|
3622
3630
|
).datetime({ offset: true }).describe(
|
|
@@ -3625,8 +3633,7 @@ const microsoft_graph_onlineMeeting = z.object({
|
|
|
3625
3633
|
isEndToEndEncryptionEnabled: z.boolean().describe("Indicates whether end-to-end encryption (E2EE) is enabled for the online meeting.").nullish(),
|
|
3626
3634
|
isEntryExitAnnounced: z.boolean().describe("Indicates whether to announce when callers join or leave.").nullish(),
|
|
3627
3635
|
joinInformation: microsoft_graph_itemBody.optional(),
|
|
3628
|
-
joinMeetingIdSettings: microsoft_graph_joinMeetingIdSettings.optional()
|
|
3629
|
-
joinWebUrl: z.string().describe("The join URL of the online meeting. Read-only.").nullish()
|
|
3636
|
+
joinMeetingIdSettings: microsoft_graph_joinMeetingIdSettings.optional()
|
|
3630
3637
|
}).passthrough().passthrough();
|
|
3631
3638
|
const microsoft_graph_onlineMeetingCollectionResponse = z.object({
|
|
3632
3639
|
"@odata.count": z.number().int().nullable(),
|
|
@@ -4549,8 +4556,8 @@ const microsoft_graph_baseItem = z.object({
|
|
|
4549
4556
|
webUrl: z.string().describe(
|
|
4550
4557
|
"URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
|
|
4551
4558
|
).nullish(),
|
|
4552
|
-
createdByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
4553
|
-
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
4559
|
+
createdByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
4560
|
+
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional()
|
|
4554
4561
|
}).passthrough();
|
|
4555
4562
|
const microsoft_graph_site = z.object({
|
|
4556
4563
|
id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
|
|
@@ -4571,8 +4578,8 @@ const microsoft_graph_site = z.object({
|
|
|
4571
4578
|
webUrl: z.string().describe(
|
|
4572
4579
|
"URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
|
|
4573
4580
|
).nullish(),
|
|
4574
|
-
createdByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
4575
|
-
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
4581
|
+
createdByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
4582
|
+
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
4576
4583
|
isPersonalSite: z.boolean().describe("Identifies whether the site is personal or not. Read-only.").nullish(),
|
|
4577
4584
|
root: microsoft_graph_root.optional(),
|
|
4578
4585
|
sharepointIds: microsoft_graph_sharepointIds.optional(),
|
|
@@ -4692,6 +4699,7 @@ const microsoft_graph_virtualEventSession = z.object({
|
|
|
4692
4699
|
audioConferencing: microsoft_graph_audioConferencing.optional(),
|
|
4693
4700
|
chatInfo: microsoft_graph_chatInfo.optional(),
|
|
4694
4701
|
chatRestrictions: microsoft_graph_chatRestrictions.optional(),
|
|
4702
|
+
cloudVideoInteropInfo: microsoft_graph_cloudVideoInteropInfo.optional(),
|
|
4695
4703
|
expiryDateTime: z.string().regex(
|
|
4696
4704
|
/^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$/
|
|
4697
4705
|
).datetime({ offset: true }).describe(
|
|
@@ -4700,8 +4708,7 @@ const microsoft_graph_virtualEventSession = z.object({
|
|
|
4700
4708
|
isEndToEndEncryptionEnabled: z.boolean().describe("Indicates whether end-to-end encryption (E2EE) is enabled for the online meeting.").nullish(),
|
|
4701
4709
|
isEntryExitAnnounced: z.boolean().describe("Indicates whether to announce when callers join or leave.").nullish(),
|
|
4702
4710
|
joinInformation: microsoft_graph_itemBody.optional(),
|
|
4703
|
-
joinMeetingIdSettings: microsoft_graph_joinMeetingIdSettings.optional()
|
|
4704
|
-
joinWebUrl: z.string().describe("The join URL of the online meeting. Read-only.").nullish()
|
|
4711
|
+
joinMeetingIdSettings: microsoft_graph_joinMeetingIdSettings.optional()
|
|
4705
4712
|
}).passthrough().passthrough();
|
|
4706
4713
|
const microsoft_graph_meetingAudience = z.enum(["everyone", "organization", "unknownFutureValue"]);
|
|
4707
4714
|
const microsoft_graph_communicationsUserIdentity = z.object({
|
|
@@ -4723,10 +4730,14 @@ const microsoft_graph_virtualEventRegistrationQuestionBase = z.object({
|
|
|
4723
4730
|
const microsoft_graph_virtualEventWebinarRegistrationConfiguration = z.object({
|
|
4724
4731
|
id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
|
|
4725
4732
|
capacity: z.number().gte(-2147483648).lte(2147483647).describe("Total capacity of the virtual event.").nullish(),
|
|
4733
|
+
isManualApprovalEnabled: z.boolean().describe(
|
|
4734
|
+
"Indicates whether registrations require organizer approval before a participant is confirmed."
|
|
4735
|
+
).nullish(),
|
|
4736
|
+
isWaitlistEnabled: z.boolean().describe(
|
|
4737
|
+
"Indicates whether more registrants are automatically placed on a waitlist when capacity is reached."
|
|
4738
|
+
).nullish(),
|
|
4726
4739
|
registrationWebUrl: z.string().describe("Registration URL of the virtual event.").nullish(),
|
|
4727
|
-
questions: z.array(microsoft_graph_virtualEventRegistrationQuestionBase).describe("Registration questions.").optional()
|
|
4728
|
-
isManualApprovalEnabled: z.boolean().nullish(),
|
|
4729
|
-
isWaitlistEnabled: z.boolean().nullish()
|
|
4740
|
+
questions: z.array(microsoft_graph_virtualEventRegistrationQuestionBase).describe("Registration questions.").optional()
|
|
4730
4741
|
}).passthrough();
|
|
4731
4742
|
const microsoft_graph_virtualEventRegistrationQuestionAnswer = z.object({
|
|
4732
4743
|
booleanValue: z.boolean().describe(
|
|
@@ -4783,6 +4794,7 @@ const microsoft_graph_virtualEventWebinar = z.object({
|
|
|
4783
4794
|
externalEventInformation: z.array(microsoft_graph_virtualEventExternalInformation).describe(
|
|
4784
4795
|
"The external information of a virtual event. Returned only for event organizers or coorganizers; otherwise, null."
|
|
4785
4796
|
).optional(),
|
|
4797
|
+
isRegistrationRequired: z.boolean().describe("Indicates whether attendee registration is enabled for the virtual event.").nullish(),
|
|
4786
4798
|
settings: microsoft_graph_virtualEventSettings.optional(),
|
|
4787
4799
|
startDateTime: microsoft_graph_dateTimeTimeZone.optional(),
|
|
4788
4800
|
status: microsoft_graph_virtualEventStatus.optional(),
|
|
@@ -5212,6 +5224,7 @@ const schemas = {
|
|
|
5212
5224
|
microsoft_graph_audioConferencing,
|
|
5213
5225
|
microsoft_graph_chatInfo,
|
|
5214
5226
|
microsoft_graph_chatRestrictions,
|
|
5227
|
+
microsoft_graph_cloudVideoInteropInfo,
|
|
5215
5228
|
microsoft_graph_joinMeetingIdSettings,
|
|
5216
5229
|
microsoft_graph_onlineMeeting,
|
|
5217
5230
|
microsoft_graph_onlineMeetingCollectionResponse,
|
|
@@ -5865,8 +5878,8 @@ const endpoints = makeApi([
|
|
|
5865
5878
|
webUrl: z.string().describe(
|
|
5866
5879
|
"URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
|
|
5867
5880
|
).nullish(),
|
|
5868
|
-
createdByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
5869
|
-
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
5881
|
+
createdByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
5882
|
+
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
5870
5883
|
audio: microsoft_graph_audio.optional(),
|
|
5871
5884
|
bundle: microsoft_graph_bundle.optional(),
|
|
5872
5885
|
cTag: z.string().describe(
|
|
@@ -5980,8 +5993,8 @@ const endpoints = makeApi([
|
|
|
5980
5993
|
webUrl: z.string().describe(
|
|
5981
5994
|
"URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
|
|
5982
5995
|
).nullish(),
|
|
5983
|
-
createdByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
5984
|
-
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from
|
|
5996
|
+
createdByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
5997
|
+
lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 137 properties to 25 most common ones]").optional(),
|
|
5985
5998
|
audio: microsoft_graph_audio.optional(),
|
|
5986
5999
|
bundle: microsoft_graph_bundle.optional(),
|
|
5987
6000
|
cTag: z.string().describe(
|
|
@@ -11185,6 +11198,7 @@ resource.`,
|
|
|
11185
11198
|
audioConferencing: microsoft_graph_audioConferencing.optional(),
|
|
11186
11199
|
chatInfo: microsoft_graph_chatInfo.optional(),
|
|
11187
11200
|
chatRestrictions: microsoft_graph_chatRestrictions.optional(),
|
|
11201
|
+
cloudVideoInteropInfo: microsoft_graph_cloudVideoInteropInfo.optional(),
|
|
11188
11202
|
expiryDateTime: z.string().regex(
|
|
11189
11203
|
/^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$/
|
|
11190
11204
|
).datetime({ offset: true }).describe(
|
|
@@ -11195,8 +11209,7 @@ resource.`,
|
|
|
11195
11209
|
).nullish(),
|
|
11196
11210
|
isEntryExitAnnounced: z.boolean().describe("Indicates whether to announce when callers join or leave.").nullish(),
|
|
11197
11211
|
joinInformation: microsoft_graph_itemBody.optional(),
|
|
11198
|
-
joinMeetingIdSettings: microsoft_graph_joinMeetingIdSettings.optional()
|
|
11199
|
-
joinWebUrl: z.string().describe("The join URL of the online meeting. Read-only.").nullish()
|
|
11212
|
+
joinMeetingIdSettings: microsoft_graph_joinMeetingIdSettings.optional()
|
|
11200
11213
|
}).passthrough().passthrough()
|
|
11201
11214
|
}
|
|
11202
11215
|
],
|
|
@@ -11257,6 +11270,7 @@ resource.`,
|
|
|
11257
11270
|
audioConferencing: microsoft_graph_audioConferencing.optional(),
|
|
11258
11271
|
chatInfo: microsoft_graph_chatInfo.optional(),
|
|
11259
11272
|
chatRestrictions: microsoft_graph_chatRestrictions.optional(),
|
|
11273
|
+
cloudVideoInteropInfo: microsoft_graph_cloudVideoInteropInfo.optional(),
|
|
11260
11274
|
expiryDateTime: z.string().regex(
|
|
11261
11275
|
/^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$/
|
|
11262
11276
|
).datetime({ offset: true }).describe(
|
|
@@ -11267,8 +11281,7 @@ resource.`,
|
|
|
11267
11281
|
).nullish(),
|
|
11268
11282
|
isEntryExitAnnounced: z.boolean().describe("Indicates whether to announce when callers join or leave.").nullish(),
|
|
11269
11283
|
joinInformation: microsoft_graph_itemBody.optional(),
|
|
11270
|
-
joinMeetingIdSettings: microsoft_graph_joinMeetingIdSettings.optional()
|
|
11271
|
-
joinWebUrl: z.string().describe("The join URL of the online meeting. Read-only.").nullish()
|
|
11284
|
+
joinMeetingIdSettings: microsoft_graph_joinMeetingIdSettings.optional()
|
|
11272
11285
|
}).passthrough().passthrough()
|
|
11273
11286
|
}
|
|
11274
11287
|
],
|
package/dist/graph-tools.js
CHANGED
|
@@ -20,6 +20,7 @@ import { TOOL_CATEGORIES } from "./tool-categories.js";
|
|
|
20
20
|
import { getRequestTokens } from "./request-context.js";
|
|
21
21
|
import { parseTeamsUrl } from "./lib/teams-url-parser.js";
|
|
22
22
|
import { buildBM25Index, scoreQuery, tokenize } from "./lib/bm25.js";
|
|
23
|
+
import { deriveTargetResource } from "./audit-target-resource.js";
|
|
23
24
|
import { describeToolSchema, describeUtilityToolSchema } from "./lib/tool-schema.js";
|
|
24
25
|
import {
|
|
25
26
|
TOP_UNSUPPORTED_DELTA_TOOLS,
|
|
@@ -598,6 +599,7 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
598
599
|
const startTime = Date.now();
|
|
599
600
|
const upn = getUserIdentityForAudit(getRequestTokens()?.accessToken);
|
|
600
601
|
const httpMethod = tool.method.toUpperCase();
|
|
602
|
+
let targetResource;
|
|
601
603
|
try {
|
|
602
604
|
const accountParam = params.account;
|
|
603
605
|
const accountModeError = await checkAccountParamInBearerMode(accountParam, authManager);
|
|
@@ -815,6 +817,10 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
815
817
|
if (accountAccessToken) {
|
|
816
818
|
options.accessToken = accountAccessToken;
|
|
817
819
|
}
|
|
820
|
+
targetResource = deriveTargetResource({
|
|
821
|
+
pathPattern: config?.pathPattern ?? tool.path,
|
|
822
|
+
params
|
|
823
|
+
});
|
|
818
824
|
const { accessToken: _redacted, ...safeOptions } = options;
|
|
819
825
|
logger.info(
|
|
820
826
|
`Making graph request to ${path2} with options: ${JSON.stringify(safeOptions)}${_redacted ? " [accessToken=REDACTED]" : ""}`
|
|
@@ -915,7 +921,8 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
915
921
|
tool: tool.alias,
|
|
916
922
|
http_method: httpMethod,
|
|
917
923
|
status: response.isError ? "error" : "success",
|
|
918
|
-
duration_ms: Date.now() - startTime
|
|
924
|
+
duration_ms: Date.now() - startTime,
|
|
925
|
+
...targetResource ? { target_resource: targetResource } : {}
|
|
919
926
|
});
|
|
920
927
|
return {
|
|
921
928
|
content,
|
|
@@ -933,6 +940,7 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
933
940
|
http_method: httpMethod,
|
|
934
941
|
status: "error",
|
|
935
942
|
duration_ms: Date.now() - startTime,
|
|
943
|
+
...targetResource ? { target_resource: targetResource } : {},
|
|
936
944
|
error_type: err?.name || "Error",
|
|
937
945
|
error_code: err?.status ?? err?.code
|
|
938
946
|
});
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "
|
|
2
|
+
import { loadEnvFile } from "./load-env.js";
|
|
3
3
|
import { parseArgs } from "./cli.js";
|
|
4
4
|
import logger from "./logger.js";
|
|
5
5
|
import AuthManager, { buildAllowedScopeDiagnostics, resolveAuthScopes } from "./auth.js";
|
|
@@ -33,6 +33,7 @@ process.on("uncaughtException", (err, origin) => {
|
|
|
33
33
|
});
|
|
34
34
|
async function main() {
|
|
35
35
|
try {
|
|
36
|
+
loadEnvFile();
|
|
36
37
|
const args = parseArgs();
|
|
37
38
|
const includeWorkScopes = args.orgMode || false;
|
|
38
39
|
if (includeWorkScopes) {
|
package/dist/load-env.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { parse } from "dotenv";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const ENV_FILE_ALLOWLIST = [
|
|
5
|
+
"MS365_MCP_CLIENT_ID",
|
|
6
|
+
"MS365_MCP_CLIENT_SECRET",
|
|
7
|
+
"MS365_MCP_TENANT_ID",
|
|
8
|
+
"MS365_MCP_CLOUD_TYPE"
|
|
9
|
+
];
|
|
10
|
+
function loadEnvFile(options = {}) {
|
|
11
|
+
const file = options.path ?? path.resolve(process.cwd(), ".env");
|
|
12
|
+
let fromFile = {};
|
|
13
|
+
try {
|
|
14
|
+
fromFile = parse(fs.readFileSync(file, "utf8"));
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
const applied = [];
|
|
18
|
+
const ignored = [];
|
|
19
|
+
for (const [key, value] of Object.entries(fromFile)) {
|
|
20
|
+
if (!ENV_FILE_ALLOWLIST.includes(key)) {
|
|
21
|
+
ignored.push(key);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (process.env[key] !== void 0) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
process.env[key] = value;
|
|
28
|
+
applied.push(key);
|
|
29
|
+
}
|
|
30
|
+
const ignoredOwn = ignored.filter((key) => key.startsWith("MS365_MCP_"));
|
|
31
|
+
if (ignoredOwn.length > 0) {
|
|
32
|
+
console.error(
|
|
33
|
+
`[ms365-mcp] Ignored ${ignoredOwn.length} variable(s) from .env: ${[...ignoredOwn].sort().join(", ")}. Only ${ENV_FILE_ALLOWLIST.join(", ")} are read from .env - set anything else in your shell or MCP client config.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return { applied, ignored };
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
ENV_FILE_ALLOWLIST,
|
|
40
|
+
loadEnvFile
|
|
41
|
+
};
|
|
@@ -289,6 +289,9 @@ function parseTimeoutMs(value) {
|
|
|
289
289
|
return parsed;
|
|
290
290
|
}
|
|
291
291
|
async function assertCommandUsable(commandPath) {
|
|
292
|
+
if (!path.isAbsolute(commandPath)) {
|
|
293
|
+
throw new Error(`${AUTH_CACHE_COMMAND_ENV} must be an absolute path.`);
|
|
294
|
+
}
|
|
292
295
|
let stats;
|
|
293
296
|
try {
|
|
294
297
|
stats = await fs.promises.stat(commandPath);
|
package/docs/deployment.md
CHANGED
|
@@ -213,7 +213,7 @@ The client automatically discovers OAuth endpoints and opens a browser for authe
|
|
|
213
213
|
- **Tool filtering**: use `--enabled-tools <regex>` or `--preset <names>` to restrict available tools
|
|
214
214
|
- **CORS**: configure `MS365_MCP_CORS_ORIGIN` to restrict allowed origins (defaults to `http://localhost:3000`); set explicitly when clients run on a different origin
|
|
215
215
|
- **Disable Dynamic Client Registration**: when only a known client talks to the server, set `MS365_MCP_DISABLE_DCR=true` (or pass `--no-dynamic-registration`) to close the anonymous `/register` endpoint
|
|
216
|
-
- **Structured audit log**: enabled by default. Every tool invocation emits one JSON line on
|
|
216
|
+
- **Structured audit log**: enabled by default. Every tool invocation emits one JSON line on stderr (captured by the container platform's log collector) and to `~/.ms-365-mcp-server/logs/audit.log` (mode `0o600`) with `{ event, request_id, user_principal_name, tool, http_method, status, duration_ms, target_resource?, error_type?, error_code? }`. When an audited generated Microsoft Graph tool targets a derivable resource through an ID-like path parameter such as `{message-id}` or `{driveItem-id}`, `target_resource` is `{ type, id }`, where `id` is the Graph path up to that resource ID. Later path parameters such as `{path}`, query values, tool parameters, returned content, and Graph response bodies are NEVER recorded, and error messages are reduced to `error_type` / `error_code` so upstream library errors do not leak token fragments or query-string PII. Forms the "who accessed what, when" trail required for GDPR / HIPAA / PIPEDA / SOC 2 audit. Opt-out: `MS365_MCP_AUDIT_LOG=false`
|
|
217
217
|
- **Graph resilience**: every call to Microsoft Graph is wrapped with a fetch timeout (default 100 s via `MS365_MCP_GRAPH_TIMEOUT_MS`), retry-with-backoff on 429 / 503 / 504 / network errors (default 3 retries, full-jitter exponential backoff, honours `Retry-After`; 503 / 504 / network errors only retried for idempotent methods, 429 retried on all methods), and a process-wide circuit breaker that opens after 5 consecutive failures and cools down for 30 s (`MS365_MCP_GRAPH_CIRCUIT_THRESHOLD` / `MS365_MCP_GRAPH_CIRCUIT_COOLDOWN_MS`). Disable the breaker for trusted automation: `MS365_MCP_GRAPH_CIRCUIT_DISABLED=true`
|
|
218
218
|
- **Confirm gate on destructive tools**: opt-in, **off by default**. Enable with `MS365_MCP_REQUIRE_CONFIRM=true`. When on, destructive tools (POST except `readOnly`, PATCH, PUT, DELETE — `delete-mail-message`, `send-mail`, `update-event`, etc.) return `{ "error": "confirmation_required" }` until the caller re-invokes them with `"confirm": true`. Mitigates accidental writes when an LLM misroutes a request or follows an injected instruction. Shipped opt-in so it is a non-breaking, additive layer that can coexist with client-side elicitation prompts (MCP Elicitation API) where the client supports them.
|
|
219
219
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.137.0",
|
|
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",
|