@softeria/ms-365-mcp-server 0.142.0 → 0.144.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/README.md +10 -0
- package/dist/__tests__/graph-client.test.js +128 -0
- package/dist/__tests__/graph-tools.test.js +274 -31
- package/dist/auth-tools.js +0 -1
- package/dist/cli.js +18 -0
- package/dist/graph-client.js +119 -37
- package/dist/graph-tools.js +121 -26
- package/dist/lib/message-signoff.js +230 -0
- package/package.json +4 -2
package/dist/graph-client.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getSharedBreaker,
|
|
8
8
|
loadResilienceConfig
|
|
9
9
|
} from "./lib/graph-resilience.js";
|
|
10
|
+
import { applyMessageSignoffToRequest } from "./lib/message-signoff.js";
|
|
10
11
|
import { open, stat, unlink } from "fs/promises";
|
|
11
12
|
import { pipeline } from "stream/promises";
|
|
12
13
|
function isBinaryContentType(contentType) {
|
|
@@ -30,6 +31,62 @@ function isBinaryContentType(contentType) {
|
|
|
30
31
|
}
|
|
31
32
|
return false;
|
|
32
33
|
}
|
|
34
|
+
class GraphApiError extends Error {
|
|
35
|
+
constructor(message, httpStatus, graphErrorCode) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "GraphApiError";
|
|
38
|
+
this.httpStatus = httpStatus;
|
|
39
|
+
this.graphErrorCode = graphErrorCode;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function extractGraphErrorCode(errorText) {
|
|
43
|
+
try {
|
|
44
|
+
const body = JSON.parse(errorText);
|
|
45
|
+
const code = body.error?.code ?? body.code;
|
|
46
|
+
return typeof code === "string" ? code : void 0;
|
|
47
|
+
} catch {
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function definedMetadata(metadata) {
|
|
52
|
+
return Object.fromEntries(
|
|
53
|
+
Object.entries(metadata).filter(([, value]) => value !== void 0)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
function isRecord(value) {
|
|
57
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
58
|
+
}
|
|
59
|
+
function incrementCount(counts, key) {
|
|
60
|
+
counts[key] = (counts[key] ?? 0) + 1;
|
|
61
|
+
}
|
|
62
|
+
function extractGraphErrorCodeFromBody(body) {
|
|
63
|
+
if (!isRecord(body)) return void 0;
|
|
64
|
+
const error = body.error;
|
|
65
|
+
const code = isRecord(error) ? error.code : body.code;
|
|
66
|
+
return typeof code === "string" ? code : void 0;
|
|
67
|
+
}
|
|
68
|
+
function extractBatchMetadata(data) {
|
|
69
|
+
if (!isRecord(data) || !Array.isArray(data.responses)) return {};
|
|
70
|
+
const httpStatusCounts = {};
|
|
71
|
+
const errorCodeCounts = {};
|
|
72
|
+
for (const item of data.responses) {
|
|
73
|
+
if (!isRecord(item)) continue;
|
|
74
|
+
const status = item.status;
|
|
75
|
+
if (typeof status !== "number" || !Number.isInteger(status)) continue;
|
|
76
|
+
incrementCount(httpStatusCounts, String(status));
|
|
77
|
+
if (status >= 400) {
|
|
78
|
+
const errorCode = extractGraphErrorCodeFromBody(item.body);
|
|
79
|
+
if (errorCode) {
|
|
80
|
+
incrementCount(errorCodeCounts, errorCode);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
graph_batch_subrequest_count: data.responses.length,
|
|
86
|
+
...Object.keys(httpStatusCounts).length > 0 ? { graph_batch_http_status_counts: httpStatusCounts } : {},
|
|
87
|
+
...Object.keys(errorCodeCounts).length > 0 ? { graph_batch_error_code_counts: errorCodeCounts } : {}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
33
90
|
class GraphClient {
|
|
34
91
|
constructor(authManager, secrets, outputFormat = "json") {
|
|
35
92
|
this.outputFormat = "json";
|
|
@@ -38,6 +95,9 @@ class GraphClient {
|
|
|
38
95
|
this.outputFormat = outputFormat;
|
|
39
96
|
}
|
|
40
97
|
async makeRequest(endpoint, options = {}) {
|
|
98
|
+
return (await this.makeRequestWithMetadata(endpoint, options)).data;
|
|
99
|
+
}
|
|
100
|
+
async makeRequestWithMetadata(endpoint, options = {}) {
|
|
41
101
|
const contextTokens = getRequestTokens();
|
|
42
102
|
const accessToken = options.accessToken ?? contextTokens?.accessToken ?? await this.authManager.getToken();
|
|
43
103
|
if (!accessToken) {
|
|
@@ -46,23 +106,14 @@ class GraphClient {
|
|
|
46
106
|
try {
|
|
47
107
|
const response = await this.performRequest(endpoint, accessToken, options);
|
|
48
108
|
if (response.status === 403) {
|
|
49
|
-
|
|
50
|
-
if (errorText.includes("scope") || errorText.includes("permission")) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
`Microsoft Graph API scope error: ${response.status} ${response.statusText} - ${errorText}. This tool requires organization mode. Please restart with --org-mode flag.`
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
throw new Error(
|
|
56
|
-
`Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}`
|
|
57
|
-
);
|
|
109
|
+
throw await this.createGraphApiError(response);
|
|
58
110
|
}
|
|
59
111
|
if (!response.ok) {
|
|
60
|
-
throw
|
|
61
|
-
`Microsoft Graph API error: ${response.status} ${response.statusText} - ${await response.text()}`
|
|
62
|
-
);
|
|
112
|
+
throw await this.createGraphApiError(response);
|
|
63
113
|
}
|
|
64
114
|
const contentTypeHeader = response.headers?.get?.("content-type") || "";
|
|
65
115
|
const isBinaryResponse = isBinaryContentType(contentTypeHeader);
|
|
116
|
+
let metadata = { http_status: response.status };
|
|
66
117
|
let result;
|
|
67
118
|
if (isBinaryResponse) {
|
|
68
119
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
@@ -87,16 +138,22 @@ class GraphClient {
|
|
|
87
138
|
}
|
|
88
139
|
}
|
|
89
140
|
}
|
|
141
|
+
if (endpoint === "/$batch") {
|
|
142
|
+
metadata = { ...metadata, ...extractBatchMetadata(result) };
|
|
143
|
+
}
|
|
90
144
|
if (options.includeHeaders) {
|
|
91
145
|
const etag = response.headers.get("ETag") || response.headers.get("etag");
|
|
92
146
|
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
93
147
|
return {
|
|
94
|
-
|
|
95
|
-
|
|
148
|
+
data: {
|
|
149
|
+
...result,
|
|
150
|
+
_etag: etag || "no-etag-found"
|
|
151
|
+
},
|
|
152
|
+
metadata
|
|
96
153
|
};
|
|
97
154
|
}
|
|
98
155
|
}
|
|
99
|
-
return result;
|
|
156
|
+
return { data: result, metadata };
|
|
100
157
|
} catch (error) {
|
|
101
158
|
logger.error("Microsoft Graph API request failed:", error);
|
|
102
159
|
throw error;
|
|
@@ -120,20 +177,10 @@ class GraphClient {
|
|
|
120
177
|
}
|
|
121
178
|
const response = await this.performRequest(endpoint, accessToken, options);
|
|
122
179
|
if (response.status === 403) {
|
|
123
|
-
|
|
124
|
-
if (errorText.includes("scope") || errorText.includes("permission")) {
|
|
125
|
-
throw new Error(
|
|
126
|
-
`Microsoft Graph API scope error: ${response.status} ${response.statusText} - ${errorText}. This tool requires organization mode. Please restart with --org-mode flag.`
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
throw new Error(
|
|
130
|
-
`Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}`
|
|
131
|
-
);
|
|
180
|
+
throw await this.createGraphApiError(response);
|
|
132
181
|
}
|
|
133
182
|
if (!response.ok) {
|
|
134
|
-
throw
|
|
135
|
-
`Microsoft Graph API error: ${response.status} ${response.statusText} - ${await response.text()}`
|
|
136
|
-
);
|
|
183
|
+
throw await this.createGraphApiError(response);
|
|
137
184
|
}
|
|
138
185
|
if (!response.body) {
|
|
139
186
|
throw new Error("Microsoft Graph returned an empty response body");
|
|
@@ -149,7 +196,8 @@ class GraphClient {
|
|
|
149
196
|
}
|
|
150
197
|
return {
|
|
151
198
|
contentType: response.headers.get("content-type") || "application/octet-stream",
|
|
152
|
-
contentLength
|
|
199
|
+
contentLength,
|
|
200
|
+
httpStatus: response.status
|
|
153
201
|
};
|
|
154
202
|
} catch (error) {
|
|
155
203
|
logger.error("Microsoft Graph file download failed:", error);
|
|
@@ -161,11 +209,30 @@ class GraphClient {
|
|
|
161
209
|
}
|
|
162
210
|
}
|
|
163
211
|
}
|
|
212
|
+
async createGraphApiError(response) {
|
|
213
|
+
const errorText = await response.text();
|
|
214
|
+
const graphErrorCode = extractGraphErrorCode(errorText);
|
|
215
|
+
const isPermissionOrScopeError = errorText.includes("scope") || errorText.includes("permission");
|
|
216
|
+
if (response.status === 403 && isPermissionOrScopeError) {
|
|
217
|
+
return new GraphApiError(
|
|
218
|
+
`Microsoft Graph API scope error: ${response.status} ${response.statusText} - ${errorText}. This tool requires organization mode. Please restart with --org-mode flag.`,
|
|
219
|
+
response.status,
|
|
220
|
+
graphErrorCode
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return new GraphApiError(
|
|
224
|
+
`Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}`,
|
|
225
|
+
response.status,
|
|
226
|
+
graphErrorCode
|
|
227
|
+
);
|
|
228
|
+
}
|
|
164
229
|
async performRequest(endpoint, accessToken, options) {
|
|
165
230
|
const cloudEndpoints = getCloudEndpoints(this.secrets.cloudType);
|
|
166
231
|
const apiVersion = options.apiVersion || "v1.0";
|
|
167
232
|
const url = `${cloudEndpoints.graphApi}/${apiVersion}${endpoint}`;
|
|
168
233
|
logger.info(`[GRAPH CLIENT] Final URL being sent to Microsoft: ${url}`);
|
|
234
|
+
const method = options.method || "GET";
|
|
235
|
+
const body = applyMessageSignoffToRequest(method, endpoint, options.body);
|
|
169
236
|
const headers = {
|
|
170
237
|
Authorization: `Bearer ${accessToken}`,
|
|
171
238
|
"Content-Type": "application/json",
|
|
@@ -174,10 +241,10 @@ class GraphClient {
|
|
|
174
241
|
return fetchWithResilience(
|
|
175
242
|
url,
|
|
176
243
|
{
|
|
177
|
-
method
|
|
244
|
+
method,
|
|
178
245
|
headers,
|
|
179
246
|
// Node's fetch accepts Buffer/Uint8Array; TS BodyInit doesn't.
|
|
180
|
-
body
|
|
247
|
+
body
|
|
181
248
|
},
|
|
182
249
|
loadResilienceConfig(),
|
|
183
250
|
getSharedBreaker()
|
|
@@ -208,22 +275,36 @@ class GraphClient {
|
|
|
208
275
|
logger.info(
|
|
209
276
|
`Calling ${endpoint} with options: ${JSON.stringify(safeOptions)}${_redacted ? " [accessToken=REDACTED]" : ""}`
|
|
210
277
|
);
|
|
211
|
-
const
|
|
278
|
+
const { data, metadata } = await this.makeRequestWithMetadata(endpoint, options);
|
|
212
279
|
const outputFormat = options.forceJsonOutput ? "json" : this.outputFormat;
|
|
213
|
-
return this.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
options.excludeResponse,
|
|
217
|
-
outputFormat
|
|
280
|
+
return this.withResponseMetadata(
|
|
281
|
+
this.formatJsonResponse(data, options.rawResponse, options.excludeResponse, outputFormat),
|
|
282
|
+
metadata
|
|
218
283
|
);
|
|
219
284
|
} catch (error) {
|
|
220
285
|
logger.error(`Error in Graph API request: ${error}`);
|
|
286
|
+
const metadata = error instanceof GraphApiError ? definedMetadata({
|
|
287
|
+
http_status: error.httpStatus,
|
|
288
|
+
error_code: error.graphErrorCode
|
|
289
|
+
}) : void 0;
|
|
221
290
|
return {
|
|
222
291
|
content: [{ type: "text", text: JSON.stringify({ error: error.message }) }],
|
|
223
|
-
isError: true
|
|
292
|
+
isError: true,
|
|
293
|
+
...metadata && Object.keys(metadata).length > 0 ? { _meta: metadata } : {}
|
|
224
294
|
};
|
|
225
295
|
}
|
|
226
296
|
}
|
|
297
|
+
withResponseMetadata(response, metadata) {
|
|
298
|
+
const cleanMetadata = definedMetadata(metadata);
|
|
299
|
+
if (Object.keys(cleanMetadata).length === 0) return response;
|
|
300
|
+
return {
|
|
301
|
+
...response,
|
|
302
|
+
_meta: {
|
|
303
|
+
...response._meta,
|
|
304
|
+
...cleanMetadata
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
227
308
|
formatJsonResponse(data, rawResponse = false, excludeResponse = false, outputFormat = this.outputFormat) {
|
|
228
309
|
if (excludeResponse) {
|
|
229
310
|
return {
|
|
@@ -299,6 +380,7 @@ class GraphClient {
|
|
|
299
380
|
}
|
|
300
381
|
var graph_client_default = GraphClient;
|
|
301
382
|
export {
|
|
383
|
+
GraphApiError,
|
|
302
384
|
graph_client_default as default,
|
|
303
385
|
isBinaryContentType
|
|
304
386
|
};
|
package/dist/graph-tools.js
CHANGED
|
@@ -76,6 +76,86 @@ const DEFAULT_MAX_ITEMS = 1e4;
|
|
|
76
76
|
function isConfirmGateEnabled() {
|
|
77
77
|
return process.env.MS365_MCP_REQUIRE_CONFIRM === "true";
|
|
78
78
|
}
|
|
79
|
+
function auditHttpStatus(value) {
|
|
80
|
+
if (typeof value === "number" && Number.isInteger(value)) return value;
|
|
81
|
+
if (typeof value === "string" && /^\d+$/.test(value)) return Number(value);
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
function auditErrorCode(value) {
|
|
85
|
+
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
86
|
+
}
|
|
87
|
+
function auditNonNegativeInteger(value) {
|
|
88
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
89
|
+
}
|
|
90
|
+
function auditStringNumberMap(value) {
|
|
91
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
92
|
+
const entries = Object.entries(value).filter(
|
|
93
|
+
(entry) => typeof entry[1] === "number" && Number.isInteger(entry[1]) && entry[1] >= 0
|
|
94
|
+
);
|
|
95
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
96
|
+
}
|
|
97
|
+
function graphResponseAuditFields(response) {
|
|
98
|
+
const httpStatus = auditHttpStatus(response._meta?.http_status);
|
|
99
|
+
const errorCode = response.isError ? auditErrorCode(response._meta?.error_code) : void 0;
|
|
100
|
+
const graphBatchSubrequestCount = auditNonNegativeInteger(
|
|
101
|
+
response._meta?.graph_batch_subrequest_count
|
|
102
|
+
);
|
|
103
|
+
const graphBatchHttpStatusCounts = auditStringNumberMap(
|
|
104
|
+
response._meta?.graph_batch_http_status_counts
|
|
105
|
+
);
|
|
106
|
+
const graphBatchErrorCodeCounts = auditStringNumberMap(
|
|
107
|
+
response._meta?.graph_batch_error_code_counts
|
|
108
|
+
);
|
|
109
|
+
return {
|
|
110
|
+
...httpStatus !== void 0 ? { http_status: httpStatus } : {},
|
|
111
|
+
...errorCode !== void 0 ? { error_code: errorCode } : {},
|
|
112
|
+
...graphBatchSubrequestCount !== void 0 ? { graph_batch_subrequest_count: graphBatchSubrequestCount } : {},
|
|
113
|
+
...graphBatchHttpStatusCounts !== void 0 ? { graph_batch_http_status_counts: graphBatchHttpStatusCounts } : {},
|
|
114
|
+
...graphBatchErrorCodeCounts !== void 0 ? { graph_batch_error_code_counts: graphBatchErrorCodeCounts } : {}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function thrownErrorAuditFields(error) {
|
|
118
|
+
const err = error;
|
|
119
|
+
const httpStatus = auditHttpStatus(err?.httpStatus ?? err?.status);
|
|
120
|
+
const errorCode = auditErrorCode(err?.graphErrorCode ?? err?.code ?? err?.status);
|
|
121
|
+
return {
|
|
122
|
+
...httpStatus !== void 0 ? { http_status: httpStatus } : {},
|
|
123
|
+
...errorCode !== void 0 ? { error_code: errorCode } : {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function executeUtilityTool(utility, ctx, params) {
|
|
127
|
+
const requestId = randomUUID();
|
|
128
|
+
const startTime = Date.now();
|
|
129
|
+
const upn = getUserIdentityForAudit(getRequestTokens()?.accessToken);
|
|
130
|
+
try {
|
|
131
|
+
const response = await utility.execute(params, ctx);
|
|
132
|
+
auditLog({
|
|
133
|
+
event: "tool.call",
|
|
134
|
+
request_id: requestId,
|
|
135
|
+
user_principal_name: upn,
|
|
136
|
+
tool: utility.name,
|
|
137
|
+
http_method: utility.method.toUpperCase(),
|
|
138
|
+
status: response.isError ? "error" : "success",
|
|
139
|
+
duration_ms: Date.now() - startTime,
|
|
140
|
+
...graphResponseAuditFields(response)
|
|
141
|
+
});
|
|
142
|
+
return response;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const err = error;
|
|
145
|
+
auditLog({
|
|
146
|
+
event: "tool.call",
|
|
147
|
+
request_id: requestId,
|
|
148
|
+
user_principal_name: upn,
|
|
149
|
+
tool: utility.name,
|
|
150
|
+
http_method: utility.method.toUpperCase(),
|
|
151
|
+
status: "error",
|
|
152
|
+
duration_ms: Date.now() - startTime,
|
|
153
|
+
error_type: err?.name || "Error",
|
|
154
|
+
...thrownErrorAuditFields(error)
|
|
155
|
+
});
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
79
159
|
function formatDisabledToolsForLog(disabledTools) {
|
|
80
160
|
const shown = disabledTools.slice(0, 20).map((tool) => `${tool.toolName} (missing: ${tool.missingScopes.join(", ")})`);
|
|
81
161
|
const suffix = disabledTools.length > shown.length ? `, ... +${disabledTools.length - shown.length} more` : "";
|
|
@@ -316,12 +396,15 @@ const UTILITY_TOOLS = [
|
|
|
316
396
|
bytesWritten: result.contentLength
|
|
317
397
|
})
|
|
318
398
|
}
|
|
319
|
-
]
|
|
399
|
+
],
|
|
400
|
+
...result.httpStatus !== void 0 ? { _meta: { http_status: result.httpStatus } } : {}
|
|
320
401
|
};
|
|
321
402
|
} catch (error) {
|
|
403
|
+
const metadata = thrownErrorAuditFields(error);
|
|
322
404
|
return {
|
|
323
405
|
content: [{ type: "text", text: JSON.stringify({ error: error.message }) }],
|
|
324
|
-
isError: true
|
|
406
|
+
isError: true,
|
|
407
|
+
...Object.keys(metadata).length > 0 ? { _meta: metadata } : {}
|
|
325
408
|
};
|
|
326
409
|
}
|
|
327
410
|
}
|
|
@@ -496,7 +579,8 @@ const UTILITY_TOOLS = [
|
|
|
496
579
|
})
|
|
497
580
|
}
|
|
498
581
|
],
|
|
499
|
-
isError: true
|
|
582
|
+
isError: true,
|
|
583
|
+
_meta: response._meta
|
|
500
584
|
};
|
|
501
585
|
}
|
|
502
586
|
const file = item?.file;
|
|
@@ -511,12 +595,15 @@ const UTILITY_TOOLS = [
|
|
|
511
595
|
contentType: file?.mimeType
|
|
512
596
|
})
|
|
513
597
|
}
|
|
514
|
-
]
|
|
598
|
+
],
|
|
599
|
+
_meta: response._meta
|
|
515
600
|
};
|
|
516
601
|
} catch (error) {
|
|
602
|
+
const metadata = thrownErrorAuditFields(error);
|
|
517
603
|
return {
|
|
518
604
|
content: [{ type: "text", text: JSON.stringify({ error: error.message }) }],
|
|
519
|
-
isError: true
|
|
605
|
+
isError: true,
|
|
606
|
+
...Object.keys(metadata).length > 0 ? { _meta: metadata } : {}
|
|
520
607
|
};
|
|
521
608
|
}
|
|
522
609
|
}
|
|
@@ -532,7 +619,7 @@ function registerUtilityToolWithMcp(server, utility, ctx) {
|
|
|
532
619
|
readOnlyHint: utility.readOnlyHint ?? true,
|
|
533
620
|
openWorldHint: utility.openWorldHint ?? true
|
|
534
621
|
},
|
|
535
|
-
async (params) => utility
|
|
622
|
+
async (params) => executeUtilityTool(utility, ctx, params)
|
|
536
623
|
);
|
|
537
624
|
}
|
|
538
625
|
function namesNestedBodyType(value) {
|
|
@@ -886,6 +973,11 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
886
973
|
const nextPath = url.pathname.replace(/^\/(v1\.0|beta)/, "") + url.search;
|
|
887
974
|
const nextOptions = { ...options };
|
|
888
975
|
const nextResponse = await graphClient.graphRequest(nextPath, nextOptions);
|
|
976
|
+
if (nextResponse?.isError) {
|
|
977
|
+
response = nextResponse;
|
|
978
|
+
combinedResponse = void 0;
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
889
981
|
if (nextResponse?.content?.[0]?.text) {
|
|
890
982
|
const nextJsonResponse = JSON.parse(nextResponse.content[0].text);
|
|
891
983
|
if (Array.isArray(nextJsonResponse.value)) {
|
|
@@ -900,25 +992,27 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
900
992
|
break;
|
|
901
993
|
}
|
|
902
994
|
}
|
|
903
|
-
if (
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
995
|
+
if (combinedResponse !== void 0) {
|
|
996
|
+
if (pageCount >= maxPages) {
|
|
997
|
+
logger.warn(`Reached maximum page limit (${maxPages}) for pagination`);
|
|
998
|
+
}
|
|
999
|
+
if (allItems.length >= maxItems) {
|
|
1000
|
+
logger.warn(
|
|
1001
|
+
`Reached maximum item limit (${maxItems}) for pagination \u2014 truncated at ${allItems.length} items`
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
combinedResponse.value = allItems;
|
|
1005
|
+
if (combinedResponse["@odata.count"]) {
|
|
1006
|
+
combinedResponse["@odata.count"] = allItems.length;
|
|
1007
|
+
}
|
|
1008
|
+
delete combinedResponse["@odata.nextLink"];
|
|
1009
|
+
if (deltaLink) {
|
|
1010
|
+
combinedResponse["@odata.deltaLink"] = deltaLink;
|
|
1011
|
+
}
|
|
1012
|
+
logger.info(
|
|
1013
|
+
`Pagination complete: collected ${allItems.length} items across ${pageCount} pages`
|
|
909
1014
|
);
|
|
910
1015
|
}
|
|
911
|
-
combinedResponse.value = allItems;
|
|
912
|
-
if (combinedResponse["@odata.count"]) {
|
|
913
|
-
combinedResponse["@odata.count"] = allItems.length;
|
|
914
|
-
}
|
|
915
|
-
delete combinedResponse["@odata.nextLink"];
|
|
916
|
-
if (deltaLink) {
|
|
917
|
-
combinedResponse["@odata.deltaLink"] = deltaLink;
|
|
918
|
-
}
|
|
919
|
-
logger.info(
|
|
920
|
-
`Pagination complete: collected ${allItems.length} items across ${pageCount} pages`
|
|
921
|
-
);
|
|
922
1016
|
}
|
|
923
1017
|
} catch (e) {
|
|
924
1018
|
logger.error(`Error during pagination: ${e}`);
|
|
@@ -953,7 +1047,8 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
953
1047
|
http_method: httpMethod,
|
|
954
1048
|
status: response.isError ? "error" : "success",
|
|
955
1049
|
duration_ms: Date.now() - startTime,
|
|
956
|
-
...targetResource ? { target_resource: targetResource } : {}
|
|
1050
|
+
...targetResource ? { target_resource: targetResource } : {},
|
|
1051
|
+
...graphResponseAuditFields(response)
|
|
957
1052
|
});
|
|
958
1053
|
return {
|
|
959
1054
|
content,
|
|
@@ -973,7 +1068,7 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
973
1068
|
duration_ms: Date.now() - startTime,
|
|
974
1069
|
...targetResource ? { target_resource: targetResource } : {},
|
|
975
1070
|
error_type: err?.name || "Error",
|
|
976
|
-
|
|
1071
|
+
...thrownErrorAuditFields(error)
|
|
977
1072
|
});
|
|
978
1073
|
return {
|
|
979
1074
|
content: [
|
|
@@ -1450,7 +1545,7 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
|
|
|
1450
1545
|
}
|
|
1451
1546
|
const utility = utilityByName.get(tool_name);
|
|
1452
1547
|
if (utility) {
|
|
1453
|
-
return utility
|
|
1548
|
+
return executeUtilityTool(utility, utilityCtx, parameters);
|
|
1454
1549
|
}
|
|
1455
1550
|
return {
|
|
1456
1551
|
content: [
|