226 lines
6.3 KiB
JavaScript
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';
|
|
}
|