generate-tools.jsā¢8.21 kB
#!/usr/bin/env node
/**
* Auto-generate MCP tools from Solid backend API routes
*
* This script:
* 1. Fetches all 623+ routes from backend /api/v1/_debug/routes
* 2. Categorizes them by API prefix
* 3. Generates MCP tool definitions automatically
* 4. Outputs complete tool list for the MCP server
*/
import fetch from 'node-fetch';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8090';
// Method to parameter schema mapping
const METHOD_PARAMS = {
GET: { requiresBody: false },
POST: { requiresBody: true },
PUT: { requiresBody: true },
PATCH: { requiresBody: true },
DELETE: { requiresBody: false },
};
// Extract path parameters from route
function extractPathParams(path) {
const matches = path.match(/\{([^}]+)\}/g);
if (!matches) return [];
return matches.map(m => m.slice(1, -1));
}
// Generate tool name from route path
function generateToolName(path, method) {
// Remove /api/v1/ prefix
let cleaned = path.replace(/^\/api\/v1\//, '');
// Replace path params with descriptive names
cleaned = cleaned.replace(/\{([^}]+)\}/g, 'by_$1');
// Replace slashes with dots
cleaned = cleaned.replace(/\//g, '.');
// Prefix with method for non-GET
if (method !== 'GET') {
cleaned = `${method.toLowerCase()}_${cleaned}`;
}
return cleaned;
}
// Generate description from route
function generateDescription(path, method) {
const parts = path.split('/').filter(p => p && !p.startsWith('{'));
const resource = parts[parts.length - 1] || parts[parts.length - 2] || 'resource';
const actions = {
GET: 'Get',
POST: 'Create',
PUT: 'Update',
PATCH: 'Update',
DELETE: 'Delete',
};
return `${actions[method] || 'Access'} ${resource.replace(/_/g, ' ')}`;
}
// Generate input schema from path params
function generateInputSchema(path, method) {
const pathParams = extractPathParams(path);
const properties = {};
const required = [];
// Add path parameters
pathParams.forEach(param => {
properties[param] = {
type: param.includes('id') ? 'number' : 'string',
description: `The ${param.replace(/_/g, ' ')}`,
};
required.push(param);
});
// Add common query params for GET
if (method === 'GET') {
if (path.includes('list') || path.match(/\/[^{]+$/)) {
properties.limit = {
type: 'number',
description: 'Maximum number of results to return',
default: 50,
};
properties.offset = {
type: 'number',
description: 'Offset for pagination',
default: 0,
};
}
if (path.includes('search')) {
properties.query = {
type: 'string',
description: 'Search query string',
};
}
// Add company_id filter for most endpoints
if (!path.includes('superadmin') && !path.includes('auth')) {
properties.company_id = {
type: 'number',
description: 'Filter by company/tenant ID',
};
}
}
// Add body for POST/PUT/PATCH
if (METHOD_PARAMS[method]?.requiresBody) {
properties.body = {
type: 'object',
description: 'Request body data',
};
}
return {
type: 'object',
properties,
required: required.length > 0 ? required : undefined,
};
}
// Categorize route by API prefix
function categorizeRoute(path) {
const categories = {
'/api/v1/superadmin': 'SuperAdmin - Platform Management',
'/api/v1/agents': 'AI Agents',
'/api/v1/ada': 'ADA Orchestrator',
'/api/v1/crm': 'CRM System',
'/api/v1/products': 'Product Management',
'/api/v1/inventory': 'Inventory Management',
'/api/v1/stock': 'Stock Management',
'/api/v1/locations': 'Warehouse Locations',
'/api/v1/variants': 'Product Variants',
'/api/v1/merchant': 'Merchant Configuration',
'/api/v1/performance': 'Performance Monitoring',
'/api/v1/tokens': 'Token Usage & Billing',
'/api/v1/payments': 'Payments',
'/api/v1/payment-links': 'Payment Links',
'/api/v1/pos': 'Point of Sale',
'/api/v1/processors': 'Payment Processors',
'/api/v1/billing': 'Billing & Subscriptions',
'/api/v1/orders': 'Order Management',
'/api/v1/cart': 'Shopping Cart',
'/api/v1/fulfillment': 'Order Fulfillment',
'/api/v1/shipping': 'Shipping',
'/api/v1/analytics': 'Analytics',
'/api/v1/dashboard': 'Dashboard',
'/api/v1/monitoring': 'System Monitoring',
'/api/v1/security': 'Security',
'/api/v1/domains': 'Domain Management',
'/api/v1/cms': 'Content Management',
'/api/v1/social': 'Social Media',
'/api/v1/email': 'Email Communication',
'/api/v1/chat': 'AI Chat',
'/api/v1/webhooks': 'Webhooks',
'/api/v1/onboarding': 'Onboarding',
'/api/v1/sandbox': 'Sandbox Environment',
'/api/v1/mcp': 'MCP Tools',
'/api/v1/console': 'Console',
'/api/v1/llm-providers': 'LLM Providers',
'/api/v1/auth': 'Authentication',
};
for (const [prefix, category] of Object.entries(categories)) {
if (path.startsWith(prefix)) {
return category;
}
}
return 'Other';
}
// Main generator
async function generateTools() {
console.log('š Fetching routes from backend...');
try {
const response = await fetch(`${BACKEND_URL}/api/v1/_debug/routes`);
const data = await response.json();
console.log(`ā
Found ${data.count} routes`);
const tools = [];
const categories = {};
// Process each route
data.routes.forEach(route => {
// Skip internal/debug routes
if (route.path.includes('/_debug') || route.path.includes('/_health')) {
return;
}
route.methods.forEach(method => {
if (method === 'HEAD' || method === 'OPTIONS') return;
const toolName = generateToolName(route.path, method);
const description = generateDescription(route.path, method);
const category = categorizeRoute(route.path);
const inputSchema = generateInputSchema(route.path, method);
const tool = {
name: toolName,
description: description,
category: category,
endpoint: route.path,
method: method,
inputSchema: inputSchema,
};
tools.push(tool);
// Track by category
if (!categories[category]) {
categories[category] = [];
}
categories[category].push(tool);
});
});
console.log(`\nš Generated ${tools.length} MCP tools across ${Object.keys(categories).length} categories:`);
Object.entries(categories).forEach(([cat, tools]) => {
console.log(` ${cat}: ${tools.length} tools`);
});
// Write tools to file
const outputPath = path.join(__dirname, '..', 'generated-tools.json');
fs.writeFileSync(outputPath, JSON.stringify({ tools, categories }, null, 2));
console.log(`\nā
Tools written to: ${outputPath}`);
// Generate summary
const summary = {
total_tools: tools.length,
total_categories: Object.keys(categories).length,
categories: Object.entries(categories).map(([name, tools]) => ({
name,
tool_count: tools.length,
sample_tools: tools.slice(0, 3).map(t => t.name),
})),
};
const summaryPath = path.join(__dirname, '..', 'tools-summary.json');
fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
console.log(`ā
Summary written to: ${summaryPath}`);
return { tools, categories, summary };
} catch (error) {
console.error('ā Error generating tools:', error);
throw error;
}
}
// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
generateTools()
.then(({ summary }) => {
console.log('\nš Tool generation complete!');
console.log(`\nNext steps:`);
console.log(`1. Review generated-tools.json`);
console.log(`2. Run: node scripts/build-mcp-server.js`);
console.log(`3. Test with: npm start`);
})
.catch(err => {
console.error('Fatal error:', err);
process.exit(1);
});
}
export { generateTools };