clawdbot-workspace/fix-localbosses-mcps.js

226 lines
6.3 KiB
JavaScript

#!/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';
}