@dashflow/ms365-mcp-server 1.0.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 +54 -0
- package/.releaserc.json +9 -0
- package/Dockerfile +22 -0
- package/LICENSE +21 -0
- package/README.md +590 -0
- package/bin/generate-graph-client.mjs +59 -0
- package/bin/modules/download-openapi.mjs +40 -0
- package/bin/modules/extract-descriptions.mjs +48 -0
- package/bin/modules/generate-mcp-tools.mjs +46 -0
- package/bin/modules/simplified-openapi.mjs +694 -0
- package/dist/auth-tools.js +202 -0
- package/dist/auth.js +422 -0
- package/dist/cli.js +78 -0
- package/dist/cloud-config.js +49 -0
- package/dist/endpoints.json +596 -0
- package/dist/generated/endpoint-types.js +0 -0
- package/dist/generated/hack.js +42 -0
- package/dist/graph-client.js +208 -0
- package/dist/graph-tools.js +401 -0
- package/dist/index.js +76 -0
- package/dist/lib/microsoft-auth.js +73 -0
- package/dist/logger.js +42 -0
- package/dist/oauth-provider.js +51 -0
- package/dist/request-context.js +9 -0
- package/dist/secrets.js +68 -0
- package/dist/server.js +387 -0
- package/dist/tool-categories.js +93 -0
- package/dist/version.js +10 -0
- package/eslint.config.js +43 -0
- package/glama.json +4 -0
- package/package.json +79 -0
- package/remove-recursive-refs.js +294 -0
- package/src/endpoints.json +596 -0
- package/src/generated/README.md +56 -0
- package/src/generated/endpoint-types.ts +27 -0
- package/src/generated/hack.ts +49 -0
- package/test-calendar-fix.js +62 -0
- package/test-real-calendar.js +96 -0
- package/tsup.config.ts +30 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect if a schema definition creates recursive references
|
|
12
|
+
* Handles complex recursive paths like #/definitions/X/properties/body/anyOf/1
|
|
13
|
+
*
|
|
14
|
+
* I really really hope this solves
|
|
15
|
+
* https://github.com/Softeria/ms-365-mcp-server/issues/36 and perhaps even
|
|
16
|
+
* https://github.com/Softeria/ms-365-mcp-server/issues/62
|
|
17
|
+
*
|
|
18
|
+
* Or any other silly tool that doesn't support recursive $refs
|
|
19
|
+
*
|
|
20
|
+
* Note - if the tool still struggles with $ref in general, this fix won't help!
|
|
21
|
+
*/
|
|
22
|
+
function detectRecursiveRefs(schema, definitionName) {
|
|
23
|
+
if (!schema || typeof schema !== 'object') return [];
|
|
24
|
+
|
|
25
|
+
const recursions = [];
|
|
26
|
+
const currentDefPath = `#/definitions/${definitionName}`;
|
|
27
|
+
|
|
28
|
+
function findAllRefs(obj, path = []) {
|
|
29
|
+
const refs = [];
|
|
30
|
+
|
|
31
|
+
function traverse(current, currentPath) {
|
|
32
|
+
if (!current || typeof current !== 'object') return;
|
|
33
|
+
|
|
34
|
+
if (Array.isArray(current)) {
|
|
35
|
+
current.forEach((item, index) => traverse(item, [...currentPath, index]));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (current.$ref) {
|
|
40
|
+
refs.push({
|
|
41
|
+
ref: current.$ref,
|
|
42
|
+
path: currentPath.join('.'),
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Object.entries(current).forEach(([key, value]) => {
|
|
48
|
+
traverse(value, [...currentPath, key]);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
traverse(obj, path);
|
|
53
|
+
return refs;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const allRefs = findAllRefs(schema);
|
|
57
|
+
|
|
58
|
+
for (const refInfo of allRefs) {
|
|
59
|
+
const ref = refInfo.ref;
|
|
60
|
+
|
|
61
|
+
if (ref.startsWith(currentDefPath)) {
|
|
62
|
+
recursions.push({
|
|
63
|
+
path: refInfo.path,
|
|
64
|
+
ref: ref,
|
|
65
|
+
type: 'recursive_reference',
|
|
66
|
+
});
|
|
67
|
+
} else if (ref === currentDefPath) {
|
|
68
|
+
recursions.push({
|
|
69
|
+
path: refInfo.path,
|
|
70
|
+
ref: ref,
|
|
71
|
+
type: 'direct_self_reference',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return recursions;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function removeRecursiveProperties(schema, recursions) {
|
|
80
|
+
if (!schema || typeof schema !== 'object' || recursions.length === 0) {
|
|
81
|
+
return schema;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cleaned = JSON.parse(JSON.stringify(schema));
|
|
85
|
+
|
|
86
|
+
const propertiesToRemove = new Set();
|
|
87
|
+
|
|
88
|
+
for (const recursion of recursions) {
|
|
89
|
+
const pathParts = recursion.path.split('.').filter((p) => p !== '');
|
|
90
|
+
|
|
91
|
+
if (pathParts[pathParts.length - 1] === 'items' && pathParts.length > 1) {
|
|
92
|
+
const propertyPath = pathParts.slice(0, -1).join('.');
|
|
93
|
+
propertiesToRemove.add(propertyPath);
|
|
94
|
+
} else {
|
|
95
|
+
propertiesToRemove.add(recursion.path);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const sortedPaths = Array.from(propertiesToRemove).sort(
|
|
100
|
+
(a, b) => b.split('.').length - a.split('.').length
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
for (const propertyPath of sortedPaths) {
|
|
104
|
+
const pathParts = propertyPath.split('.');
|
|
105
|
+
|
|
106
|
+
let current = cleaned;
|
|
107
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
108
|
+
const part = pathParts[i];
|
|
109
|
+
if (current && typeof current === 'object' && part in current) {
|
|
110
|
+
current = current[part];
|
|
111
|
+
} else {
|
|
112
|
+
current = null;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (current && typeof current === 'object') {
|
|
118
|
+
const propertyName = pathParts[pathParts.length - 1];
|
|
119
|
+
if (propertyName in current) {
|
|
120
|
+
console.log(` Removing recursive property: ${propertyPath}`);
|
|
121
|
+
delete current[propertyName];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return cleaned;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Process a tool to remove recursive references while keeping other $refs
|
|
131
|
+
*/
|
|
132
|
+
function processToolSchema(tool) {
|
|
133
|
+
if (!tool.inputSchema || !tool.inputSchema.definitions) {
|
|
134
|
+
return tool;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const definitions = tool.inputSchema.definitions;
|
|
138
|
+
const processedDefinitions = {};
|
|
139
|
+
let totalRecursionsRemoved = 0;
|
|
140
|
+
|
|
141
|
+
console.log(`\nš§ Processing ${tool.name}:`);
|
|
142
|
+
|
|
143
|
+
for (const [defName, defSchema] of Object.entries(definitions)) {
|
|
144
|
+
const recursions = detectRecursiveRefs(defSchema, defName);
|
|
145
|
+
|
|
146
|
+
if (recursions.length > 0) {
|
|
147
|
+
console.log(` Found ${recursions.length} recursive references in ${defName}`);
|
|
148
|
+
processedDefinitions[defName] = removeRecursiveProperties(defSchema, recursions);
|
|
149
|
+
totalRecursionsRemoved += recursions.length;
|
|
150
|
+
} else {
|
|
151
|
+
processedDefinitions[defName] = defSchema;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const cleanedTool = {
|
|
156
|
+
...tool,
|
|
157
|
+
inputSchema: {
|
|
158
|
+
...tool.inputSchema,
|
|
159
|
+
definitions: processedDefinitions,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
console.log(` āļø Removed ${totalRecursionsRemoved} recursive references`);
|
|
164
|
+
return cleanedTool;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function removeRecursiveRefs() {
|
|
168
|
+
try {
|
|
169
|
+
console.log('āļø Removing Recursive References (Keeping Other $refs)\n');
|
|
170
|
+
console.log('='.repeat(60));
|
|
171
|
+
|
|
172
|
+
const inputPath = join(__dirname, 'schemas-with-refs-direct.json');
|
|
173
|
+
if (!fs.existsSync(inputPath)) {
|
|
174
|
+
throw new Error('Schema file not found. Run extract-schemas-direct.js first.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const originalData = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
|
|
178
|
+
const tools = originalData.result?.tools || [];
|
|
179
|
+
|
|
180
|
+
console.log(`Processing ${tools.length} tools...`);
|
|
181
|
+
|
|
182
|
+
const processedTools = tools.map(processToolSchema);
|
|
183
|
+
|
|
184
|
+
const cleanedData = {
|
|
185
|
+
...originalData,
|
|
186
|
+
result: {
|
|
187
|
+
...originalData.result,
|
|
188
|
+
tools: processedTools,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const outputPath = join(__dirname, 'schemas-properties-removed.json');
|
|
193
|
+
const cleanedString = JSON.stringify(cleanedData, null, 2);
|
|
194
|
+
fs.writeFileSync(outputPath, cleanedString);
|
|
195
|
+
|
|
196
|
+
console.log(`\nš¾ Cleaned schemas saved to: ${outputPath}`);
|
|
197
|
+
|
|
198
|
+
const originalString = JSON.stringify(originalData);
|
|
199
|
+
const originalRefs = (originalString.match(/\$ref/g) || []).length;
|
|
200
|
+
const cleanedRefs = (cleanedString.match(/\$ref/g) || []).length;
|
|
201
|
+
const removedRefs = originalRefs - cleanedRefs;
|
|
202
|
+
|
|
203
|
+
console.log('\nš CLEANING ANALYSIS');
|
|
204
|
+
console.log('-'.repeat(40));
|
|
205
|
+
console.log(
|
|
206
|
+
`Original size: ${originalString.length.toLocaleString()} chars (${(originalString.length / 1024).toFixed(2)} KB)`
|
|
207
|
+
);
|
|
208
|
+
console.log(
|
|
209
|
+
`Cleaned size: ${cleanedString.length.toLocaleString()} chars (${(cleanedString.length / 1024).toFixed(2)} KB)`
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const sizeDiff = cleanedString.length - originalString.length;
|
|
213
|
+
const sizeChange = ((sizeDiff / originalString.length) * 100).toFixed(1);
|
|
214
|
+
console.log(`Size change: ${sizeDiff.toLocaleString()} chars (${sizeChange}%)`);
|
|
215
|
+
|
|
216
|
+
console.log(`\nOriginal $refs: ${originalRefs}`);
|
|
217
|
+
console.log(`Cleaned $refs: ${cleanedRefs}`);
|
|
218
|
+
console.log(`Removed $refs: ${removedRefs}`);
|
|
219
|
+
console.log(`Refs remaining: ${((cleanedRefs / originalRefs) * 100).toFixed(1)}%`);
|
|
220
|
+
|
|
221
|
+
console.log('\nš§Ŗ QUICK RECURSION CHECK');
|
|
222
|
+
console.log('-'.repeat(40));
|
|
223
|
+
|
|
224
|
+
const sampleRecursiveTools = [
|
|
225
|
+
'create-calendar-event',
|
|
226
|
+
'update-calendar-event',
|
|
227
|
+
'create-onenote-page',
|
|
228
|
+
];
|
|
229
|
+
let foundRecursions = 0;
|
|
230
|
+
|
|
231
|
+
for (const toolName of sampleRecursiveTools) {
|
|
232
|
+
const tool = processedTools.find((t) => t.name === toolName);
|
|
233
|
+
if (tool) {
|
|
234
|
+
const toolString = JSON.stringify(tool);
|
|
235
|
+
const recursivePattern = `#/definitions/${toolName}Parameters/properties/body/anyOf/1`;
|
|
236
|
+
if (toolString.includes(recursivePattern)) {
|
|
237
|
+
foundRecursions++;
|
|
238
|
+
console.log(` ā ${toolName}: Still contains recursive pattern`);
|
|
239
|
+
} else {
|
|
240
|
+
console.log(` ā
${toolName}: Recursive pattern removed`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (foundRecursions === 0) {
|
|
246
|
+
console.log('\nā
No recursive patterns found in sample tools!');
|
|
247
|
+
} else {
|
|
248
|
+
console.log(`\nā ļø ${foundRecursions} tools still contain recursive patterns`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log('\nš” SUMMARY');
|
|
252
|
+
console.log('='.repeat(40));
|
|
253
|
+
|
|
254
|
+
if (removedRefs > 0) {
|
|
255
|
+
console.log(`ā
Successfully removed ${removedRefs} recursive references`);
|
|
256
|
+
console.log(`ā
Kept ${cleanedRefs} non-recursive $ref references`);
|
|
257
|
+
|
|
258
|
+
if (sizeDiff < 0) {
|
|
259
|
+
console.log(`ā
Reduced schema size by ${Math.abs(sizeDiff).toLocaleString()} characters`);
|
|
260
|
+
} else if (sizeDiff < originalString.length * 0.1) {
|
|
261
|
+
console.log(`ā
Minimal size increase (${sizeChange}%)`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
console.log('\nš BENEFITS:');
|
|
265
|
+
console.log('⢠Eliminates infinite recursion during flattening');
|
|
266
|
+
console.log('⢠Preserves beneficial $ref references for shared types');
|
|
267
|
+
console.log('⢠May allow partial flattening for LangChain compatibility');
|
|
268
|
+
console.log('⢠Reduces schema complexity while maintaining functionality');
|
|
269
|
+
} else {
|
|
270
|
+
console.log('ā¹ļø No recursive references found to remove');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const remainingRefTypes = new Set();
|
|
274
|
+
cleanedString.match(/"#\/definitions\/[^"]+"/g)?.forEach((ref) => {
|
|
275
|
+
const defName = ref.split('/').pop()?.replace('"', '');
|
|
276
|
+
if (defName) remainingRefTypes.add(defName);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
console.log(`\nš Remaining reference types: ${remainingRefTypes.size}`);
|
|
280
|
+
if (remainingRefTypes.size <= 10) {
|
|
281
|
+
console.log('Sample remaining refs:', Array.from(remainingRefTypes).slice(0, 5).join(', '));
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error('Error removing recursive refs:', error.message);
|
|
285
|
+
console.error(error.stack);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export { removeRecursiveRefs };
|
|
291
|
+
|
|
292
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
293
|
+
removeRecursiveRefs();
|
|
294
|
+
}
|