@softeria/ms-365-mcp-server 0.11.5 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,8 @@ import fs from 'fs';
2
2
  import yaml from 'js-yaml';
3
3
 
4
4
  export function createAndSaveSimplifiedOpenAPI(endpointsFile, openapiFile, openapiTrimmedFile) {
5
- const endpoints = JSON.parse(fs.readFileSync(endpointsFile, 'utf8'));
5
+ const allEndpoints = JSON.parse(fs.readFileSync(endpointsFile, 'utf8'));
6
+ const endpoints = allEndpoints.filter((endpoint) => !endpoint.disabled);
6
7
 
7
8
  const spec = fs.readFileSync(openapiFile, 'utf8');
8
9
  const openApiSpec = yaml.load(spec);
@@ -39,6 +40,10 @@ export function createAndSaveSimplifiedOpenAPI(endpointsFile, openapiFile, opena
39
40
  simplifyAnyOfInPaths(openApiSpec.paths);
40
41
  }
41
42
 
43
+ console.log('🧹 Pruning unused schemas...');
44
+ const usedSchemas = findUsedSchemas(openApiSpec);
45
+ pruneUnusedSchemas(openApiSpec, usedSchemas);
46
+
42
47
  fs.writeFileSync(openapiTrimmedFile, yaml.dump(openApiSpec));
43
48
  }
44
49
 
@@ -50,327 +55,256 @@ function removeODataTypeRecursively(obj) {
50
55
  return;
51
56
  }
52
57
 
53
- if (obj.properties && obj.properties['@odata.type']) {
54
- delete obj.properties['@odata.type'];
55
- }
56
-
57
- if (obj.required && Array.isArray(obj.required)) {
58
- const typeIndex = obj.required.indexOf('@odata.type');
59
- if (typeIndex !== -1) {
60
- obj.required.splice(typeIndex, 1);
61
- if (obj.required.length === 0) {
62
- delete obj.required;
63
- }
64
- }
65
- }
66
-
67
- if (obj.properties) {
68
- removeODataTypeRecursively(obj.properties);
69
- Object.values(obj.properties).forEach((prop) => removeODataTypeRecursively(prop));
70
- }
71
-
72
- if (obj.additionalProperties && typeof obj.additionalProperties === 'object') {
73
- removeODataTypeRecursively(obj.additionalProperties);
74
- }
75
-
76
- if (obj.items) {
77
- removeODataTypeRecursively(obj.items);
78
- }
79
-
80
- ['allOf', 'anyOf', 'oneOf'].forEach((key) => {
81
- if (obj[key] && Array.isArray(obj[key])) {
82
- obj[key].forEach((item) => removeODataTypeRecursively(item));
83
- }
84
- });
85
-
86
58
  Object.keys(obj).forEach((key) => {
87
- if (typeof obj[key] === 'object' && obj[key] !== null) {
59
+ if (key === '@odata.type') {
60
+ delete obj[key];
61
+ } else {
88
62
  removeODataTypeRecursively(obj[key]);
89
63
  }
90
64
  });
91
65
  }
92
66
 
93
- function flattenComplexSchemasRecursively(schemas) {
94
- console.log('Flattening complex schemas for better client compatibility...');
95
-
96
- let flattenedCount = 0;
67
+ function simplifyAnyOfInPaths(paths) {
68
+ Object.entries(paths).forEach(([pathKey, pathItem]) => {
69
+ if (!pathItem || typeof pathItem !== 'object') return;
97
70
 
98
- Object.keys(schemas).forEach((schemaName) => {
99
- const schema = schemas[schemaName];
71
+ Object.entries(pathItem).forEach(([method, operation]) => {
72
+ if (!operation || typeof operation !== 'object') return;
100
73
 
101
- if (schema.allOf && Array.isArray(schema.allOf) && schema.allOf.length <= 5) {
102
- try {
103
- const flattened = { type: 'object', properties: {} };
104
- const required = new Set();
105
-
106
- for (const subSchema of schema.allOf) {
107
- if (subSchema.$ref && subSchema.$ref.startsWith('#/components/schemas/')) {
108
- const refName = subSchema.$ref.replace('#/components/schemas/', '');
109
- if (schemaName === 'microsoft.graph.attendee') {
110
- console.log(
111
- `Processing ref ${refName} for attendee, exists: ${!!schemas[refName]}, has properties: ${!!schemas[refName]?.properties}`
112
- );
113
- if (schemas[refName]?.properties) {
114
- console.log(`Properties in ${refName}:`, Object.keys(schemas[refName].properties));
115
- }
116
- }
117
- if (schemas[refName] && schemas[refName].properties) {
118
- Object.assign(flattened.properties, schemas[refName].properties);
119
- if (schemas[refName].required) {
120
- schemas[refName].required.forEach((req) => required.add(req));
121
- }
122
- }
123
- } else if (subSchema.properties) {
124
- Object.assign(flattened.properties, subSchema.properties);
125
- if (subSchema.required) {
126
- subSchema.required.forEach((req) => required.add(req));
127
- }
74
+ if (operation.parameters && Array.isArray(operation.parameters)) {
75
+ operation.parameters.forEach((param) => {
76
+ if (param.schema && param.schema.anyOf) {
77
+ simplifyAnyOfSchema(param.schema, `Path ${pathKey} ${method} parameter`);
128
78
  }
79
+ });
80
+ }
129
81
 
130
- Object.keys(subSchema).forEach((key) => {
131
- if (!['allOf', 'properties', 'required', '$ref'].includes(key) && !flattened[key]) {
132
- flattened[key] = subSchema[key];
133
- }
134
- });
135
- }
136
-
137
- if (schema.properties) {
138
- Object.assign(flattened.properties, schema.properties);
139
- }
140
-
141
- if (schema.required) {
142
- schema.required.forEach((req) => required.add(req));
143
- }
144
-
145
- Object.keys(schema).forEach((key) => {
146
- if (!['allOf', 'properties', 'required'].includes(key) && !flattened[key]) {
147
- flattened[key] = schema[key];
82
+ if (operation.requestBody && operation.requestBody.content) {
83
+ Object.entries(operation.requestBody.content).forEach(([mediaType, mediaTypeObj]) => {
84
+ if (mediaTypeObj.schema && mediaTypeObj.schema.anyOf) {
85
+ simplifyAnyOfSchema(
86
+ mediaTypeObj.schema,
87
+ `Path ${pathKey} ${method} requestBody ${mediaType}`
88
+ );
148
89
  }
149
90
  });
91
+ }
150
92
 
151
- if (required.size > 0) {
152
- flattened.required = Array.from(required);
153
- }
154
-
155
- schemas[schemaName] = flattened;
156
- flattenedCount++;
157
-
158
- if (schemaName === 'microsoft.graph.attendee') {
159
- console.log('Ensuring attendee has all required properties from attendeeBase');
160
- const attendeeBase = schemas['microsoft.graph.attendeeBase'];
161
- if (attendeeBase && attendeeBase.properties) {
162
- if (!flattened.properties.emailAddress && attendeeBase.properties.emailAddress) {
163
- flattened.properties.emailAddress = attendeeBase.properties.emailAddress;
164
- console.log('Added emailAddress property to attendee');
165
- }
166
- if (!flattened.properties.type && attendeeBase.properties.type) {
167
- flattened.properties.type = attendeeBase.properties.type;
168
- console.log('Added type property to attendee');
169
- }
93
+ if (operation.responses) {
94
+ Object.entries(operation.responses).forEach(([statusCode, response]) => {
95
+ if (response.content) {
96
+ Object.entries(response.content).forEach(([mediaType, mediaTypeObj]) => {
97
+ if (mediaTypeObj.schema && mediaTypeObj.schema.anyOf) {
98
+ simplifyAnyOfSchema(
99
+ mediaTypeObj.schema,
100
+ `Path ${pathKey} ${method} response ${statusCode} ${mediaType}`
101
+ );
102
+ }
103
+ });
170
104
  }
171
- }
172
- } catch (error) {
173
- console.warn(`Warning: Could not flatten schema ${schemaName}:`, error.message);
105
+ });
174
106
  }
175
- }
107
+ });
108
+ });
109
+ }
176
110
 
177
- if (schema.anyOf && Array.isArray(schema.anyOf)) {
178
- if (schema.anyOf.length === 2) {
179
- const hasRef = schema.anyOf.some((item) => item.$ref);
180
- const hasNullableObject = schema.anyOf.some(
181
- (item) =>
182
- item.type === 'object' && item.nullable === true && Object.keys(item).length <= 2
183
- );
111
+ function simplifyAnyOfSchema(schema, context) {
112
+ if (!schema.anyOf || !Array.isArray(schema.anyOf)) return;
184
113
 
185
- if (hasRef && hasNullableObject) {
186
- console.log(`Simplifying anyOf in ${schemaName} (ref + nullable object pattern)`);
187
- const refItem = schema.anyOf.find((item) => item.$ref);
188
- const simplified = { ...refItem };
189
- simplified.nullable = true;
190
- Object.keys(schema).forEach((key) => {
191
- if (!['anyOf'].includes(key) && !simplified[key]) {
192
- simplified[key] = schema[key];
193
- }
194
- });
195
- schemas[schemaName] = simplified;
196
- flattenedCount++;
197
- }
198
- } else if (schema.anyOf.length > 2) {
199
- console.log(`Simplifying anyOf in ${schemaName} (${schema.anyOf.length} -> 1 option)`);
200
- const simplified = { ...schema.anyOf[0] };
201
- simplified.nullable = true;
202
- simplified.description = `Simplified from ${schema.anyOf.length} anyOf options`;
203
- schemas[schemaName] = simplified;
204
- flattenedCount++;
205
- }
206
- }
114
+ const anyOfItems = schema.anyOf;
207
115
 
208
- if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length > 2) {
209
- console.log(`Simplifying oneOf in ${schemaName} (${schema.oneOf.length} -> 1 option)`);
210
- const simplified = { ...schema.oneOf[0] };
211
- simplified.nullable = true;
212
- simplified.description = `Simplified from ${schema.oneOf.length} oneOf options`;
213
- schemas[schemaName] = simplified;
214
- flattenedCount++;
215
- }
116
+ if (anyOfItems.length === 2) {
117
+ const hasRef = anyOfItems.some((item) => item.$ref);
118
+ const hasNullableObject = anyOfItems.some(
119
+ (item) => item.type === 'object' && item.nullable === true && Object.keys(item).length <= 2
120
+ );
216
121
 
217
- if (schema.properties && Object.keys(schema.properties).length > 25) {
218
- console.log(
219
- `Reducing properties in ${schemaName} (${Object.keys(schema.properties).length} -> 25)`
220
- );
221
- const priorityProperties = {};
222
- const allKeys = Object.keys(schema.properties);
122
+ if (hasRef && hasNullableObject) {
123
+ console.log(`Simplifying anyOf in ${context} (ref + nullable object pattern)`);
124
+ const refItem = anyOfItems.find((item) => item.$ref);
125
+ delete schema.anyOf;
126
+ schema.$ref = refItem.$ref;
127
+ schema.nullable = true;
128
+ }
129
+ } else if (anyOfItems.length > 2) {
130
+ console.log(`Simplifying anyOf in ${context} (multiple options)`);
131
+ schema.type = anyOfItems[0].type || 'object';
132
+ schema.nullable = true;
133
+ schema.description = `${schema.description || ''} [Simplified from ${
134
+ anyOfItems.length
135
+ } options]`.trim();
136
+ delete schema.anyOf;
137
+ }
138
+ }
223
139
 
224
- if (schema.required) {
225
- schema.required.forEach((key) => {
226
- if (schema.properties[key]) {
227
- priorityProperties[key] = schema.properties[key];
228
- }
229
- });
230
- }
140
+ function flattenComplexSchemasRecursively(schemas) {
141
+ Object.entries(schemas).forEach(([schemaName, schema]) => {
142
+ if (!schema || typeof schema !== 'object') return;
231
143
 
232
- const remainingSlots = 25 - Object.keys(priorityProperties).length;
233
- allKeys.slice(0, remainingSlots).forEach((key) => {
234
- if (!priorityProperties[key]) {
235
- priorityProperties[key] = schema.properties[key];
236
- }
237
- });
144
+ flattenComplexSchema(schema, schemaName);
238
145
 
239
- schema.properties = priorityProperties;
240
- schema.description =
241
- `${schema.description || ''} [Simplified: showing ${Object.keys(priorityProperties).length} of ${allKeys.length} properties]`.trim();
242
- flattenedCount++;
146
+ if (schema.allOf) {
147
+ const flattenedSchema = mergeAllOfSchemas(schema.allOf, schemas);
148
+ Object.assign(schema, flattenedSchema);
149
+ delete schema.allOf;
243
150
  }
244
151
 
245
- if (schema.properties) {
246
- simplifyNestedPropertiesRecursively(schema.properties, 0, 4);
152
+ if (schema.properties && shouldReduceProperties(schema)) {
153
+ reduceProperties(schema, schemaName);
247
154
  }
248
- });
249
155
 
250
- Object.keys(schemas).forEach((schemaName) => {
251
- const schema = schemas[schemaName];
252
156
  if (schema.properties) {
253
- Object.keys(schema.properties).forEach((propName) => {
254
- const prop = schema.properties[propName];
255
- if (prop && prop.anyOf && Array.isArray(prop.anyOf) && prop.anyOf.length === 2) {
256
- const hasRef = prop.anyOf.some((item) => item.$ref);
257
- const hasNullableObject = prop.anyOf.some(
258
- (item) =>
259
- item.type === 'object' && item.nullable === true && Object.keys(item).length <= 2
260
- );
261
-
262
- if (hasRef && hasNullableObject) {
263
- console.log(
264
- `Simplifying anyOf in ${schemaName}.${propName} (ref + nullable object pattern)`
265
- );
266
- const refItem = prop.anyOf.find((item) => item.$ref);
267
- delete prop.anyOf;
268
- prop.$ref = refItem.$ref;
269
- prop.nullable = true;
270
- flattenedCount++;
271
- }
272
- }
273
- });
157
+ simplifyNestedPropertiesRecursively(schema.properties);
274
158
  }
275
159
  });
160
+ }
276
161
 
277
- console.log(`Flattened ${flattenedCount} complex schemas`);
278
-
279
- console.log('Second pass: Fixing inheritance dependencies...');
280
- if (schemas['microsoft.graph.attendee'] && schemas['microsoft.graph.attendeeBase']) {
281
- const attendee = schemas['microsoft.graph.attendee'];
282
- const attendeeBase = schemas['microsoft.graph.attendeeBase'];
162
+ function flattenComplexSchema(schema, schemaName) {
163
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
164
+ if (schema.anyOf.length === 2) {
165
+ const hasRef = schema.anyOf.some((item) => item.$ref);
166
+ const hasNullableObject = schema.anyOf.some(
167
+ (item) => item.type === 'object' && item.nullable === true && Object.keys(item).length <= 2
168
+ );
283
169
 
284
- if (!attendee.properties.emailAddress && attendeeBase.properties?.emailAddress) {
285
- attendee.properties.emailAddress = attendeeBase.properties.emailAddress;
286
- console.log('Fixed: Added emailAddress to attendee from attendeeBase');
287
- }
288
- if (!attendee.properties.type && attendeeBase.properties?.type) {
289
- attendee.properties.type = attendeeBase.properties.type;
290
- console.log('Fixed: Added type to attendee from attendeeBase');
170
+ if (hasRef && hasNullableObject) {
171
+ console.log(`Simplifying anyOf in ${schemaName} (ref + nullable object pattern)`);
172
+ const refItem = schema.anyOf.find((item) => item.$ref);
173
+ delete schema.anyOf;
174
+ schema.$ref = refItem.$ref;
175
+ schema.nullable = true;
176
+ }
177
+ } else if (schema.anyOf.length > 2) {
178
+ console.log(`Simplifying anyOf in ${schemaName} (${schema.anyOf.length} options)`);
179
+ const firstOption = schema.anyOf[0];
180
+ schema.type = firstOption.type || 'object';
181
+ schema.nullable = true;
182
+ schema.description = `${schema.description || ''} [Simplified from ${
183
+ schema.anyOf.length
184
+ } options]`.trim();
185
+ delete schema.anyOf;
291
186
  }
292
187
  }
188
+
189
+ if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length > 2) {
190
+ console.log(`Simplifying oneOf in ${schemaName} (${schema.oneOf.length} options)`);
191
+ const firstOption = schema.oneOf[0];
192
+ schema.type = firstOption.type || 'object';
193
+ schema.nullable = true;
194
+ schema.description = `${schema.description || ''} [Simplified from ${
195
+ schema.oneOf.length
196
+ } options]`.trim();
197
+ delete schema.oneOf;
198
+ }
293
199
  }
294
200
 
295
- function simplifyAnyOfInPaths(paths) {
296
- console.log('Simplifying anyOf patterns in API paths...');
297
- let simplifiedCount = 0;
298
-
299
- Object.keys(paths).forEach((path) => {
300
- const pathItem = paths[path];
301
- Object.keys(pathItem).forEach((method) => {
302
- const operation = pathItem[method];
303
- if (operation && typeof operation === 'object') {
304
- if (operation.responses) {
305
- Object.keys(operation.responses).forEach((statusCode) => {
306
- const response = operation.responses[statusCode];
307
- if (response && response.content) {
308
- Object.keys(response.content).forEach((contentType) => {
309
- const mediaType = response.content[contentType];
310
- if (mediaType && mediaType.schema) {
311
- simplifiedCount += simplifyAnyOfPattern(
312
- mediaType.schema,
313
- `${path}.${method}.${statusCode}`
314
- );
315
- }
316
- });
317
- }
318
- });
319
- }
201
+ function shouldReduceProperties(schema) {
202
+ if (!schema.properties) return false;
203
+ const propertyCount = Object.keys(schema.properties).length;
204
+ return propertyCount > 25;
205
+ }
320
206
 
321
- if (operation.requestBody && operation.requestBody.content) {
322
- Object.keys(operation.requestBody.content).forEach((contentType) => {
323
- const mediaType = operation.requestBody.content[contentType];
324
- if (mediaType && mediaType.schema) {
325
- simplifiedCount += simplifyAnyOfPattern(
326
- mediaType.schema,
327
- `${path}.${method}.requestBody`
328
- );
329
- }
330
- });
331
- }
207
+ function reduceProperties(schema, schemaName) {
208
+ const properties = schema.properties;
209
+ const propertyCount = Object.keys(properties).length;
210
+
211
+ if (propertyCount > 25) {
212
+ console.log(`Reducing properties in ${schemaName} (${propertyCount} -> 25)`);
213
+
214
+ const priorityProperties = [
215
+ 'id',
216
+ 'name',
217
+ 'displayName',
218
+ 'description',
219
+ 'createdDateTime',
220
+ 'lastModifiedDateTime',
221
+ 'status',
222
+ 'state',
223
+ 'type',
224
+ 'value',
225
+ 'email',
226
+ 'userPrincipalName',
227
+ 'title',
228
+ 'content',
229
+ 'body',
230
+ 'subject',
231
+ 'message',
232
+ 'error',
233
+ 'code',
234
+ 'details',
235
+ 'url',
236
+ 'href',
237
+ 'path',
238
+ 'method',
239
+ 'enabled',
240
+ ];
241
+
242
+ const keptProperties = {};
243
+ const propertyKeys = Object.keys(properties);
244
+
245
+ priorityProperties.forEach((key) => {
246
+ if (properties[key]) {
247
+ keptProperties[key] = properties[key];
332
248
  }
333
249
  });
334
- });
335
-
336
- console.log(`Simplified ${simplifiedCount} anyOf patterns in paths`);
337
- }
338
-
339
- function simplifyAnyOfPattern(obj, context = '') {
340
- let count = 0;
341
250
 
342
- if (!obj || typeof obj !== 'object') return count;
251
+ const remainingSlots = 25 - Object.keys(keptProperties).length;
252
+ const otherKeys = propertyKeys.filter((key) => !keptProperties[key]);
343
253
 
344
- if (obj.anyOf && Array.isArray(obj.anyOf) && obj.anyOf.length === 2) {
345
- const hasRef = obj.anyOf.some((item) => item.$ref);
346
- const hasNullableObject = obj.anyOf.some(
347
- (item) => item.type === 'object' && item.nullable === true && Object.keys(item).length <= 2
348
- );
254
+ otherKeys.slice(0, remainingSlots).forEach((key) => {
255
+ keptProperties[key] = properties[key];
256
+ });
349
257
 
350
- if (hasRef && hasNullableObject) {
351
- console.log(`Simplifying anyOf in ${context} (ref + nullable object pattern)`);
352
- const refItem = obj.anyOf.find((item) => item.$ref);
353
- Object.keys(obj).forEach((key) => {
354
- if (key !== 'anyOf') delete obj[key];
355
- });
356
- Object.assign(obj, refItem);
357
- obj.nullable = true;
358
- delete obj.anyOf;
359
- count++;
360
- }
258
+ schema.properties = keptProperties;
259
+ schema.additionalProperties = true;
260
+ schema.description = `${
261
+ schema.description || ''
262
+ } [Note: Simplified from ${propertyCount} properties to 25 most common ones]`.trim();
361
263
  }
264
+ }
362
265
 
363
- Object.keys(obj).forEach((key) => {
364
- if (typeof obj[key] === 'object' && obj[key] !== null) {
365
- count += simplifyAnyOfPattern(obj[key], context ? `${context}.${key}` : key);
266
+ function mergeAllOfSchemas(allOfArray, allSchemas) {
267
+ const merged = {
268
+ type: 'object',
269
+ properties: {},
270
+ };
271
+
272
+ allOfArray.forEach((item) => {
273
+ if (item.$ref) {
274
+ const refSchemaName = item.$ref.replace('#/components/schemas/', '');
275
+ const refSchema = allSchemas[refSchemaName];
276
+ if (refSchema) {
277
+ console.log(
278
+ `Processing ref ${refSchemaName} for ${item.title}, exists: true, has properties: ${!!refSchema.properties}`
279
+ );
280
+ if (refSchema.properties) {
281
+ console.log(`Ensuring ${item.title} has all required properties from ${refSchemaName}`);
282
+ Object.assign(merged.properties, refSchema.properties);
283
+ }
284
+ if (refSchema.required) {
285
+ merged.required = [...(merged.required || []), ...refSchema.required];
286
+ }
287
+ if (refSchema.description && !merged.description) {
288
+ merged.description = refSchema.description;
289
+ }
290
+ }
291
+ } else if (item.properties) {
292
+ Object.assign(merged.properties, item.properties);
293
+ if (item.required) {
294
+ merged.required = [...(merged.required || []), ...item.required];
295
+ }
366
296
  }
367
297
  });
368
298
 
369
- return count;
299
+ if (merged.required) {
300
+ merged.required = [...new Set(merged.required)];
301
+ }
302
+
303
+ return merged;
370
304
  }
371
305
 
372
- function simplifyNestedPropertiesRecursively(properties, currentDepth, maxDepth) {
373
- if (currentDepth >= maxDepth) {
306
+ function simplifyNestedPropertiesRecursively(properties, currentDepth = 0, maxDepth = 3) {
307
+ if (!properties || typeof properties !== 'object' || currentDepth >= maxDepth) {
374
308
  return;
375
309
  }
376
310
 
@@ -422,3 +356,260 @@ function simplifyNestedPropertiesRecursively(properties, currentDepth, maxDepth)
422
356
  }
423
357
  });
424
358
  }
359
+
360
+ function findUsedSchemas(openApiSpec) {
361
+ const usedSchemas = new Set();
362
+ const schemasToProcess = [];
363
+ const schemas = openApiSpec.components?.schemas || {};
364
+ const responses = openApiSpec.components?.responses || {};
365
+ const paths = openApiSpec.paths || {};
366
+
367
+ Object.entries(paths).forEach(([, pathItem]) => {
368
+ Object.entries(pathItem).forEach(([, operation]) => {
369
+ if (typeof operation !== 'object') return;
370
+
371
+ if (operation.requestBody?.content) {
372
+ Object.values(operation.requestBody.content).forEach((content) => {
373
+ if (content.schema?.$ref) {
374
+ const schemaName = content.schema.$ref.replace('#/components/schemas/', '');
375
+ schemasToProcess.push(schemaName);
376
+ }
377
+ });
378
+ }
379
+
380
+ if (operation.responses) {
381
+ Object.entries(operation.responses).forEach(([, response]) => {
382
+ if (response.$ref) {
383
+ const responseName = response.$ref.replace('#/components/responses/', '');
384
+ const responseDefinition = responses[responseName];
385
+ if (responseDefinition?.content) {
386
+ Object.values(responseDefinition.content).forEach((content) => {
387
+ if (content.schema?.$ref) {
388
+ const schemaName = content.schema.$ref.replace('#/components/schemas/', '');
389
+ schemasToProcess.push(schemaName);
390
+ }
391
+ });
392
+ }
393
+ }
394
+
395
+ if (response.content) {
396
+ Object.values(response.content).forEach((content) => {
397
+ if (content.schema?.$ref) {
398
+ const schemaName = content.schema.$ref.replace('#/components/schemas/', '');
399
+ schemasToProcess.push(schemaName);
400
+ }
401
+ });
402
+ }
403
+ });
404
+ }
405
+
406
+ if (operation.parameters) {
407
+ operation.parameters.forEach((param) => {
408
+ if (param.schema?.$ref) {
409
+ const schemaName = param.schema.$ref.replace('#/components/schemas/', '');
410
+ schemasToProcess.push(schemaName);
411
+ }
412
+ });
413
+ }
414
+ });
415
+ });
416
+
417
+ const visited = new Set();
418
+
419
+ function processSchema(schemaName) {
420
+ if (visited.has(schemaName)) return;
421
+ visited.add(schemaName);
422
+
423
+ const schema = schemas[schemaName];
424
+ if (!schema) {
425
+ console.log(`⚠️ Warning: Schema ${schemaName} not found`);
426
+ return;
427
+ }
428
+
429
+ usedSchemas.add(schemaName);
430
+
431
+ findRefsInObject(schema, (ref) => {
432
+ const refSchemaName = ref.replace('#/components/schemas/', '');
433
+ if (schemas[refSchemaName]) {
434
+ processSchema(refSchemaName);
435
+ } else {
436
+ console.log(`⚠️ Schema ${schemaName} references missing schema: ${refSchemaName}`);
437
+ }
438
+ });
439
+ }
440
+
441
+ schemasToProcess.forEach((schemaName) => processSchema(schemaName));
442
+
443
+ [
444
+ 'microsoft.graph.ODataErrors.ODataError',
445
+ 'microsoft.graph.ODataErrors.MainError',
446
+ 'microsoft.graph.ODataErrors.ErrorDetails',
447
+ 'microsoft.graph.ODataErrors.InnerError',
448
+ ].forEach((errorSchema) => {
449
+ if (schemas[errorSchema]) {
450
+ processSchema(errorSchema);
451
+ }
452
+ });
453
+
454
+ console.log(
455
+ ` Found ${usedSchemas.size} used schemas out of ${Object.keys(schemas).length} total schemas`
456
+ );
457
+
458
+ return usedSchemas;
459
+ }
460
+
461
+ function findRefsInObject(obj, callback, visited = new Set()) {
462
+ if (!obj || typeof obj !== 'object' || visited.has(obj)) return;
463
+ visited.add(obj);
464
+
465
+ if (Array.isArray(obj)) {
466
+ obj.forEach((item) => findRefsInObject(item, callback, visited));
467
+ return;
468
+ }
469
+
470
+ Object.entries(obj).forEach(([key, value]) => {
471
+ if (key === '$ref' && typeof value === 'string' && value.startsWith('#/components/schemas/')) {
472
+ callback(value);
473
+ } else if (typeof value === 'object') {
474
+ findRefsInObject(value, callback, visited);
475
+ }
476
+ });
477
+ }
478
+
479
+ function cleanBrokenRefs(obj, availableSchemas, visited = new Set()) {
480
+ if (!obj || typeof obj !== 'object' || visited.has(obj)) return;
481
+ visited.add(obj);
482
+
483
+ if (Array.isArray(obj)) {
484
+ for (let i = obj.length - 1; i >= 0; i--) {
485
+ const item = obj[i];
486
+ if (item && typeof item === 'object' && item.$ref) {
487
+ const refSchemaName = item.$ref.replace('#/components/schemas/', '');
488
+ if (!availableSchemas[refSchemaName]) {
489
+ console.log(` Removing broken reference: ${refSchemaName}`);
490
+ obj.splice(i, 1);
491
+ }
492
+ } else if (typeof item === 'object') {
493
+ cleanBrokenRefs(item, availableSchemas, visited);
494
+ }
495
+ }
496
+ return;
497
+ }
498
+
499
+ Object.entries(obj).forEach(([key, value]) => {
500
+ if (key === '$ref' && typeof value === 'string' && value.startsWith('#/components/schemas/')) {
501
+ const refSchemaName = value.replace('#/components/schemas/', '');
502
+ if (!availableSchemas[refSchemaName]) {
503
+ console.log(` Removing broken $ref: ${refSchemaName}`);
504
+ delete obj[key];
505
+ if (Object.keys(obj).length === 0) {
506
+ obj.type = 'object';
507
+ }
508
+ }
509
+ } else if (typeof value === 'object') {
510
+ cleanBrokenRefs(value, availableSchemas, visited);
511
+ }
512
+ });
513
+ }
514
+
515
+ function pruneUnusedSchemas(openApiSpec, usedSchemas) {
516
+ const schemas = openApiSpec.components?.schemas || {};
517
+ const originalCount = Object.keys(schemas).length;
518
+
519
+ Object.keys(schemas).forEach((schemaName) => {
520
+ if (!usedSchemas.has(schemaName)) {
521
+ delete schemas[schemaName];
522
+ }
523
+ });
524
+
525
+ Object.values(schemas).forEach((schema) => {
526
+ if (schema) {
527
+ cleanBrokenRefs(schema, schemas);
528
+ }
529
+ });
530
+
531
+ if (openApiSpec.components?.responses) {
532
+ Object.values(openApiSpec.components.responses).forEach((response) => {
533
+ if (response) {
534
+ cleanBrokenRefs(response, schemas);
535
+ }
536
+ });
537
+ }
538
+
539
+ if (openApiSpec.paths) {
540
+ Object.values(openApiSpec.paths).forEach((pathItem) => {
541
+ if (pathItem) {
542
+ cleanBrokenRefs(pathItem, schemas);
543
+ }
544
+ });
545
+ }
546
+
547
+ const newCount = Object.keys(schemas).length;
548
+ const reduction = (((originalCount - newCount) / originalCount) * 100).toFixed(1);
549
+
550
+ console.log(` Removed ${originalCount - newCount} unused schemas (${reduction}% reduction)`);
551
+ console.log(` Final schema count: ${newCount} (from ${originalCount})`);
552
+
553
+ if (openApiSpec.components?.responses) {
554
+ const usedResponses = new Set();
555
+
556
+ Object.values(openApiSpec.paths || {}).forEach((pathItem) => {
557
+ Object.values(pathItem).forEach((operation) => {
558
+ if (operation.responses) {
559
+ Object.values(operation.responses).forEach((response) => {
560
+ if (response.$ref) {
561
+ const responseName = response.$ref.replace('#/components/responses/', '');
562
+ usedResponses.add(responseName);
563
+ }
564
+ });
565
+ }
566
+ });
567
+ });
568
+
569
+ usedResponses.add('error');
570
+
571
+ const responses = openApiSpec.components.responses;
572
+ const originalResponseCount = Object.keys(responses).length;
573
+
574
+ Object.keys(responses).forEach((responseName) => {
575
+ if (!usedResponses.has(responseName)) {
576
+ delete responses[responseName];
577
+ }
578
+ });
579
+
580
+ const newResponseCount = Object.keys(responses).length;
581
+ console.log(
582
+ ` Removed ${originalResponseCount - newResponseCount} unused responses (from ${originalResponseCount} to ${newResponseCount})`
583
+ );
584
+ }
585
+
586
+ if (openApiSpec.components?.requestBodies) {
587
+ const usedRequestBodies = new Set();
588
+
589
+ Object.values(openApiSpec.paths || {}).forEach((pathItem) => {
590
+ Object.values(pathItem).forEach((operation) => {
591
+ if (operation.requestBody?.$ref) {
592
+ const requestBodyName = operation.requestBody.$ref.replace(
593
+ '#/components/requestBodies/',
594
+ ''
595
+ );
596
+ usedRequestBodies.add(requestBodyName);
597
+ }
598
+ });
599
+ });
600
+
601
+ const requestBodies = openApiSpec.components.requestBodies;
602
+ const originalRequestBodyCount = Object.keys(requestBodies).length;
603
+
604
+ Object.keys(requestBodies).forEach((requestBodyName) => {
605
+ if (!usedRequestBodies.has(requestBodyName)) {
606
+ delete requestBodies[requestBodyName];
607
+ }
608
+ });
609
+
610
+ const newRequestBodyCount = Object.keys(requestBodies).length;
611
+ console.log(
612
+ ` Removed ${originalRequestBodyCount - newRequestBodyCount} unused request bodies (from ${originalRequestBodyCount} to ${newRequestBodyCount})`
613
+ );
614
+ }
615
+ }