#!/usr/bin/env node /** * Add _meta labels to LocalBosses MCPs that need them */ import fs from 'fs'; import path from 'path'; const MCPs = [ { name: 'ghl', path: '/Users/jakeshore/.clawdbot/workspace/mcp-diagrams/GoHighLevel-MCP/src/server.ts', categories: { contact: 'contacts', deal: 'deals', opportunity: 'deals', pipeline: 'deals', calendar: 'calendar', appointment: 'calendar', workflow: 'workflows', campaign: 'campaigns', form: 'forms', conversation: 'messaging', location: 'settings', user: 'settings', tag: 'contacts', custom_field: 'contacts', custom_value: 'contacts', note: 'contacts', task: 'tasks', } }, { name: 'google-ads', path: '/Users/jakeshore/.clawdbot/workspace/mcp-diagrams/google-ads-mcp/src/server.ts', categories: { campaign: 'campaigns', ad_group: 'ads', ad: 'ads', keyword: 'keywords', audience: 'audiences', analytics: 'analytics', report: 'analytics', conversion: 'analytics', budget: 'campaigns', bid: 'keywords', } }, { name: 'meta-ads', path: '/Users/jakeshore/.clawdbot/workspace/meta-ads-mcp/src/server.ts', categories: { campaign: 'campaigns', adset: 'ads', ad: 'ads', creative: 'creative', audience: 'audiences', insights: 'analytics', analytics: 'analytics', pixel: 'tracking', conversion: 'analytics', } }, { name: 'google-console', path: '/Users/jakeshore/.clawdbot/workspace/google-console-mcp/src/server.ts', categories: { index: 'indexing', url: 'urls', sitemap: 'sitemaps', inspect: 'urls', search: 'search', analytics: 'analytics', crawl: 'indexing', coverage: 'indexing', } }, { name: 'twilio', path: '/Users/jakeshore/.clawdbot/workspace/twilio-mcp/src/server.ts', categories: { message: 'messaging', sms: 'messaging', call: 'calls', phone: 'phone-numbers', number: 'phone-numbers', verify: 'verification', verification: 'verification', } }, ]; console.log('\n🔧 Adding _meta labels to LocalBosses MCPs...\n'); for (const mcp of MCPs) { console.log(`\n📦 Processing ${mcp.name.toUpperCase()}`); if (!fs.existsSync(mcp.path)) { console.log(` ❌ File not found: ${mcp.path}`); continue; } let content = fs.readFileSync(mcp.path, 'utf-8'); // Already has _meta? Skip if (content.includes('_meta')) { console.log(' ⏭️ Already has _meta labels, skipping'); continue; } // Find all tool definitions using regex // Pattern: { name: "tool_name", description: "...", inputSchema: {...} } const toolPattern = /{\s*name:\s*["']([^"']+)["'],\s*description:\s*["']([^"']+)["'],\s*inputSchema:\s*{/g; let match; let modifications = []; while ((match = toolPattern.exec(content)) !== null) { const toolName = match[1]; const startIndex = match.index; // Find the closing brace of this tool let braceCount = 1; let i = startIndex + match[0].length; let foundInputSchema = false; while (i < content.length && braceCount > 0) { if (content[i] === '{') braceCount++; if (content[i] === '}') { braceCount--; if (braceCount === 1 && !foundInputSchema) { // This is the end of inputSchema foundInputSchema = true; } } i++; } // Now we're at the end of the tool object // Check if there's already a _meta (shouldn't be, but be safe) const toolText = content.substring(startIndex, i); if (toolText.includes('_meta')) { console.log(` ⏭️ Tool "${toolName}" already has _meta`); continue; } // Determine category, access, complexity const category = determineCategory(toolName, mcp.categories); const access = determineAccess(toolName); const complexity = determineComplexity(toolName); // Find where to insert (before the closing brace) const insertPos = i - 1; const metaLabel = `,\n _meta: {\n labels: {\n category: "${category}",\n access: "${access}",\n complexity: "${complexity}",\n },\n }`; modifications.push({ position: insertPos, text: metaLabel, toolName, category, access, complexity, }); } if (modifications.length === 0) { console.log(' ⚠️ No tools found to modify'); continue; } // Sort modifications by position (reverse) so we can apply from end to start modifications.sort((a, b) => b.position - a.position); // Apply modifications for (const mod of modifications) { content = content.slice(0, mod.position) + mod.text + content.slice(mod.position); console.log(` ✅ Added _meta to "${mod.toolName}" (${mod.category}/${mod.access}/${mod.complexity})`); } // Write back fs.writeFileSync(mcp.path, content); console.log(` 💾 Saved ${modifications.length} modifications`); } console.log('\n✨ Done!\n'); // Helper functions function determineCategory(toolName, categoryMap) { const lower = toolName.toLowerCase(); // Try exact matches first for (const [keyword, category] of Object.entries(categoryMap)) { if (lower.includes(keyword)) { return category; } } // Fallback return 'general'; } function determineAccess(toolName) { const lower = toolName.toLowerCase(); if (lower.startsWith('delete_') || lower.startsWith('remove_')) return 'delete'; if (lower.startsWith('create_') || lower.startsWith('add_') || lower.startsWith('send_') || lower.startsWith('update_') || lower.startsWith('set_')) return 'write'; if (lower.startsWith('list_') || lower.startsWith('get_') || lower.startsWith('search_') || lower.startsWith('fetch_')) return 'read'; // Default to read for ambiguous cases return 'read'; } function determineComplexity(toolName) { const lower = toolName.toLowerCase(); // Batch operations if (lower.includes('batch') || lower.includes('bulk') || lower.includes('multi')) return 'batch'; // Complex operations if (lower.includes('report') || lower.includes('analytics') || lower.includes('insights') || lower.includes('sync')) return 'complex'; // Simple operations return 'simple'; }