#!/usr/bin/env node /** * Automatically add _meta labels to all tools across all servers */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const serversDir = path.join(__dirname, 'mcp-servers'); // Category mapping based on tool name patterns const getCategoryFromToolName = (toolName, serverName) => { // Check server type first if (serverName.includes('freshdesk') || serverName.includes('zendesk') || serverName.includes('helpscout')) { if (toolName.includes('ticket')) return 'support'; if (toolName.includes('contact') || toolName.includes('customer')) return 'contacts'; if (toolName.includes('agent') || toolName.includes('user')) return 'team'; return 'support'; } if (serverName.includes('servicetitan') || serverName.includes('housecall') || serverName.includes('jobber') || serverName.includes('fieldedge')) { if (toolName.includes('job') || toolName.includes('work_order')) return 'jobs'; if (toolName.includes('customer') || toolName.includes('client')) return 'customers'; if (toolName.includes('invoice')) return 'billing'; if (toolName.includes('technician') || toolName.includes('employee')) return 'team'; if (toolName.includes('appointment') || toolName.includes('schedule')) return 'scheduling'; return 'jobs'; } if (serverName.includes('mailchimp') || serverName.includes('constant-contact') || serverName.includes('brevo')) { if (toolName.includes('campaign')) return 'campaigns'; if (toolName.includes('contact') || toolName.includes('subscriber')) return 'contacts'; if (toolName.includes('list')) return 'lists'; if (toolName.includes('template')) return 'templates'; return 'campaigns'; } if (serverName.includes('pipedrive') || serverName.includes('close')) { if (toolName.includes('deal') || toolName.includes('opportunity')) return 'deals'; if (toolName.includes('person') || toolName.includes('contact') || toolName.includes('lead')) return 'contacts'; if (toolName.includes('activity')) return 'activities'; return 'crm'; } if (serverName.includes('trello') || serverName.includes('clickup') || serverName.includes('wrike') || serverName.includes('basecamp')) { if (toolName.includes('board') || toolName.includes('project')) return 'projects'; if (toolName.includes('task')) return 'tasks'; if (toolName.includes('comment')) return 'collaboration'; return 'projects'; } if (serverName.includes('gusto') || serverName.includes('rippling') || serverName.includes('bamboohr')) { if (toolName.includes('employee')) return 'employees'; if (toolName.includes('payroll')) return 'payroll'; if (toolName.includes('benefit')) return 'benefits'; if (toolName.includes('time_off') || toolName.includes('leave')) return 'time-off'; return 'hr'; } if (serverName.includes('toast') || serverName.includes('square') || serverName.includes('clover') || serverName.includes('lightspeed') || serverName.includes('touchbistro')) { if (toolName.includes('order') || toolName.includes('check')) return 'orders'; if (toolName.includes('menu') || toolName.includes('item')) return 'menu'; if (toolName.includes('employee') || toolName.includes('staff')) return 'team'; if (toolName.includes('reservation')) return 'reservations'; return 'pos'; } if (serverName.includes('calendly') || serverName.includes('acuity')) { if (toolName.includes('event') || toolName.includes('appointment')) return 'scheduling'; if (toolName.includes('availability')) return 'availability'; if (toolName.includes('calendar')) return 'calendars'; return 'scheduling'; } // Generic patterns if (toolName.includes('contact') || toolName.includes('customer') || toolName.includes('client') || toolName.includes('person')) return 'contacts'; if (toolName.includes('deal') || toolName.includes('opportunity')) return 'deals'; if (toolName.includes('invoice') || toolName.includes('payment')) return 'billing'; if (toolName.includes('task')) return 'tasks'; if (toolName.includes('project')) return 'projects'; if (toolName.includes('calendar') || toolName.includes('event') || toolName.includes('appointment')) return 'calendar'; if (toolName.includes('campaign')) return 'campaigns'; if (toolName.includes('report') || toolName.includes('analytics') || toolName.includes('stats')) return 'analytics'; if (toolName.includes('employee') || toolName.includes('team') || toolName.includes('user')) return 'team'; return 'general'; }; // Access type from tool name const getAccessType = (toolName) => { if (toolName.startsWith('list_') || toolName.startsWith('get_') || toolName.startsWith('search_')) return 'read'; if (toolName.startsWith('create_') || toolName.startsWith('add_')) return 'write'; if (toolName.startsWith('update_') || toolName.startsWith('modify_')) return 'write'; if (toolName.startsWith('delete_') || toolName.startsWith('remove_') || toolName.startsWith('void_') || toolName.startsWith('cancel_') || toolName.startsWith('archive_')) return 'delete'; if (toolName.startsWith('send_')) return 'write'; return 'read'; // default to read }; // Complexity from tool name const getComplexity = (toolName) => { if (toolName.startsWith('list_') || toolName.startsWith('get_')) return 'simple'; if (toolName.startsWith('search_')) return 'simple'; if (toolName.startsWith('create_') || toolName.startsWith('update_')) return 'simple'; return 'simple'; // Most operations are simple }; // Get all server directories const serverDirs = fs.readdirSync(serversDir).filter(dir => { const fullPath = path.join(serversDir, dir); return fs.statSync(fullPath).isDirectory() && fs.existsSync(path.join(fullPath, 'src/index.ts')); }); console.log(`\n🔧 Fixing ${serverDirs.length} MCP servers...\n`); let totalFixed = 0; for (const serverName of serverDirs) { const indexPath = path.join(serversDir, serverName, 'src/index.ts'); let content = fs.readFileSync(indexPath, 'utf-8'); let modified = false; let fixedCount = 0; // Find all tool definitions const toolRegex = /{\s*name:\s*["']([^"']+)["']\s*,\s*description:\s*["']([^"']+)["']\s*,\s*inputSchema:\s*{[\s\S]*?}(\s*,?\s*)}(?=\s*,?\s*{?\s*name:|\s*\])/g; content = content.replace(toolRegex, (match, toolName, description) => { // Check if already has _meta if (match.includes('_meta')) { return match; } // Get labels const category = getCategoryFromToolName(toolName, serverName); const access = getAccessType(toolName); const complexity = getComplexity(toolName); // Add _meta before closing brace const metaSection = `,\n _meta: {\n labels: {\n category: "${category}",\n access: "${access}",\n complexity: "${complexity}",\n },\n }`; // Find the last } before the closing of the tool object const lastBraceIndex = match.lastIndexOf('}'); const modifiedMatch = match.slice(0, lastBraceIndex) + metaSection + '\n ' + match.slice(lastBraceIndex); modified = true; fixedCount++; return modifiedMatch; }); if (modified) { fs.writeFileSync(indexPath, content, 'utf-8'); console.log(`✅ ${serverName}: Fixed ${fixedCount} tools`); totalFixed += fixedCount; } else { console.log(`⏭️ ${serverName}: No changes needed`); } } console.log(`\n✨ Total tools fixed: ${totalFixed}\n`);