Skip to main content
Glama

Microsoft 365 MCP Server

by Softeria
remove-recursive-refs.js9.31 kB
#!/usr/bin/env node import fs from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); /** * Detect if a schema definition creates recursive references * Handles complex recursive paths like #/definitions/X/properties/body/anyOf/1 * * I really really hope this solves * https://github.com/Softeria/ms-365-mcp-server/issues/36 and perhaps even * https://github.com/Softeria/ms-365-mcp-server/issues/62 * * Or any other silly tool that doesn't support recursive $refs * * Note - if the tool still struggles with $ref in general, this fix won't help! */ function detectRecursiveRefs(schema, definitionName) { if (!schema || typeof schema !== 'object') return []; const recursions = []; const currentDefPath = `#/definitions/${definitionName}`; function findAllRefs(obj, path = []) { const refs = []; function traverse(current, currentPath) { if (!current || typeof current !== 'object') return; if (Array.isArray(current)) { current.forEach((item, index) => traverse(item, [...currentPath, index])); return; } if (current.$ref) { refs.push({ ref: current.$ref, path: currentPath.join('.'), }); return; } Object.entries(current).forEach(([key, value]) => { traverse(value, [...currentPath, key]); }); } traverse(obj, path); return refs; } const allRefs = findAllRefs(schema); for (const refInfo of allRefs) { const ref = refInfo.ref; if (ref.startsWith(currentDefPath)) { recursions.push({ path: refInfo.path, ref: ref, type: 'recursive_reference', }); } else if (ref === currentDefPath) { recursions.push({ path: refInfo.path, ref: ref, type: 'direct_self_reference', }); } } return recursions; } function removeRecursiveProperties(schema, recursions) { if (!schema || typeof schema !== 'object' || recursions.length === 0) { return schema; } const cleaned = JSON.parse(JSON.stringify(schema)); const propertiesToRemove = new Set(); for (const recursion of recursions) { const pathParts = recursion.path.split('.').filter((p) => p !== ''); if (pathParts[pathParts.length - 1] === 'items' && pathParts.length > 1) { const propertyPath = pathParts.slice(0, -1).join('.'); propertiesToRemove.add(propertyPath); } else { propertiesToRemove.add(recursion.path); } } const sortedPaths = Array.from(propertiesToRemove).sort( (a, b) => b.split('.').length - a.split('.').length ); for (const propertyPath of sortedPaths) { const pathParts = propertyPath.split('.'); let current = cleaned; for (let i = 0; i < pathParts.length - 1; i++) { const part = pathParts[i]; if (current && typeof current === 'object' && part in current) { current = current[part]; } else { current = null; break; } } if (current && typeof current === 'object') { const propertyName = pathParts[pathParts.length - 1]; if (propertyName in current) { console.log(` Removing recursive property: ${propertyPath}`); delete current[propertyName]; } } } return cleaned; } /** * Process a tool to remove recursive references while keeping other $refs */ function processToolSchema(tool) { if (!tool.inputSchema || !tool.inputSchema.definitions) { return tool; } const definitions = tool.inputSchema.definitions; const processedDefinitions = {}; let totalRecursionsRemoved = 0; console.log(`\n🔧 Processing ${tool.name}:`); for (const [defName, defSchema] of Object.entries(definitions)) { const recursions = detectRecursiveRefs(defSchema, defName); if (recursions.length > 0) { console.log(` Found ${recursions.length} recursive references in ${defName}`); processedDefinitions[defName] = removeRecursiveProperties(defSchema, recursions); totalRecursionsRemoved += recursions.length; } else { processedDefinitions[defName] = defSchema; } } const cleanedTool = { ...tool, inputSchema: { ...tool.inputSchema, definitions: processedDefinitions, }, }; console.log(` ✂️ Removed ${totalRecursionsRemoved} recursive references`); return cleanedTool; } async function removeRecursiveRefs() { try { console.log('✂️ Removing Recursive References (Keeping Other $refs)\n'); console.log('='.repeat(60)); const inputPath = join(__dirname, 'schemas-with-refs-direct.json'); if (!fs.existsSync(inputPath)) { throw new Error('Schema file not found. Run extract-schemas-direct.js first.'); } const originalData = JSON.parse(fs.readFileSync(inputPath, 'utf8')); const tools = originalData.result?.tools || []; console.log(`Processing ${tools.length} tools...`); const processedTools = tools.map(processToolSchema); const cleanedData = { ...originalData, result: { ...originalData.result, tools: processedTools, }, }; const outputPath = join(__dirname, 'schemas-properties-removed.json'); const cleanedString = JSON.stringify(cleanedData, null, 2); fs.writeFileSync(outputPath, cleanedString); console.log(`\n💾 Cleaned schemas saved to: ${outputPath}`); const originalString = JSON.stringify(originalData); const originalRefs = (originalString.match(/\$ref/g) || []).length; const cleanedRefs = (cleanedString.match(/\$ref/g) || []).length; const removedRefs = originalRefs - cleanedRefs; console.log('\n📊 CLEANING ANALYSIS'); console.log('-'.repeat(40)); console.log( `Original size: ${originalString.length.toLocaleString()} chars (${(originalString.length / 1024).toFixed(2)} KB)` ); console.log( `Cleaned size: ${cleanedString.length.toLocaleString()} chars (${(cleanedString.length / 1024).toFixed(2)} KB)` ); const sizeDiff = cleanedString.length - originalString.length; const sizeChange = ((sizeDiff / originalString.length) * 100).toFixed(1); console.log(`Size change: ${sizeDiff.toLocaleString()} chars (${sizeChange}%)`); console.log(`\nOriginal $refs: ${originalRefs}`); console.log(`Cleaned $refs: ${cleanedRefs}`); console.log(`Removed $refs: ${removedRefs}`); console.log(`Refs remaining: ${((cleanedRefs / originalRefs) * 100).toFixed(1)}%`); console.log('\n🧪 QUICK RECURSION CHECK'); console.log('-'.repeat(40)); const sampleRecursiveTools = [ 'create-calendar-event', 'update-calendar-event', 'create-onenote-page', ]; let foundRecursions = 0; for (const toolName of sampleRecursiveTools) { const tool = processedTools.find((t) => t.name === toolName); if (tool) { const toolString = JSON.stringify(tool); const recursivePattern = `#/definitions/${toolName}Parameters/properties/body/anyOf/1`; if (toolString.includes(recursivePattern)) { foundRecursions++; console.log(` ❌ ${toolName}: Still contains recursive pattern`); } else { console.log(` ✅ ${toolName}: Recursive pattern removed`); } } } if (foundRecursions === 0) { console.log('\n✅ No recursive patterns found in sample tools!'); } else { console.log(`\n⚠️ ${foundRecursions} tools still contain recursive patterns`); } console.log('\n💡 SUMMARY'); console.log('='.repeat(40)); if (removedRefs > 0) { console.log(`✅ Successfully removed ${removedRefs} recursive references`); console.log(`✅ Kept ${cleanedRefs} non-recursive $ref references`); if (sizeDiff < 0) { console.log(`✅ Reduced schema size by ${Math.abs(sizeDiff).toLocaleString()} characters`); } else if (sizeDiff < originalString.length * 0.1) { console.log(`✅ Minimal size increase (${sizeChange}%)`); } console.log('\n📋 BENEFITS:'); console.log('• Eliminates infinite recursion during flattening'); console.log('• Preserves beneficial $ref references for shared types'); console.log('• May allow partial flattening for LangChain compatibility'); console.log('• Reduces schema complexity while maintaining functionality'); } else { console.log('ℹ️ No recursive references found to remove'); } const remainingRefTypes = new Set(); cleanedString.match(/"#\/definitions\/[^"]+"/g)?.forEach((ref) => { const defName = ref.split('/').pop()?.replace('"', ''); if (defName) remainingRefTypes.add(defName); }); console.log(`\n🔗 Remaining reference types: ${remainingRefTypes.size}`); if (remainingRefTypes.size <= 10) { console.log('Sample remaining refs:', Array.from(remainingRefTypes).slice(0, 5).join(', ')); } } catch (error) { console.error('Error removing recursive refs:', error.message); console.error(error.stack); process.exit(1); } } export { removeRecursiveRefs }; if (import.meta.url === `file://${process.argv[1]}`) { removeRecursiveRefs(); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Softeria/ms-365-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server