build-mcp-server.jsā¢8.18 kB
#!/usr/bin/env node
/**
* Build comprehensive MCP server from generated tools
*
* This script:
* 1. Reads generated-tools.json
* 2. Creates a dynamic MCP server that exposes ALL tools
* 3. Implements smart request handling for each endpoint
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
function buildServer() {
console.log('šØ Building comprehensive MCP server...');
// Read generated tools
const toolsPath = path.join(__dirname, '..', 'generated-tools.json');
if (!fs.existsSync(toolsPath)) {
console.error('ā generated-tools.json not found. Run generate-tools.js first!');
process.exit(1);
}
const { tools, categories } = JSON.parse(fs.readFileSync(toolsPath, 'utf-8'));
console.log(`š¦ Loaded ${tools.length} tools across ${Object.keys(categories).length} categories`);
// Generate MCP server code
const serverCode = `#!/usr/bin/env node
/**
* COMPREHENSIVE SOLID MCP SERVER
*
* Auto-generated from ${tools.length} backend API endpoints
* Generated: ${new Date().toISOString()}
*
* This server provides access to ALL Solid platform APIs through MCP.
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import fetch from 'node-fetch';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { readFileSync } from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load environment variables
const envPath = join(__dirname, '..', '.env');
const envContent = readFileSync(envPath, 'utf-8');
const env = {};
envContent.split('\\n').forEach(line => {
const match = line.match(/^([^=# ]+)=(.*)$/);
if (match) {
env[match[1]] = match[2].trim();
}
});
const BACKEND_URL = env.BACKEND_URL || 'http://localhost:8090';
// Helper function to make API calls
async function makeRequest(url, options = {}) {
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
const text = await response.text();
let data;
try {
data = JSON.parse(text);
} catch {
data = text;
}
return {
status: response.status,
ok: response.ok,
data,
};
} catch (error) {
return {
status: 0,
ok: false,
error: error.message,
};
}
}
// Tool registry - ${tools.length} tools
const TOOL_REGISTRY = ${JSON.stringify(tools, null, 2)};
// Create server instance
const server = new Server(
{
name: 'solid-comprehensive-mcp',
version: '2.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
const mcpTools = TOOL_REGISTRY.map(tool => ({
name: tool.name,
description: tool.description + ' [' + tool.category + ']',
inputSchema: tool.inputSchema,
}));
return {
tools: mcpTools,
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
// Find tool in registry
const tool = TOOL_REGISTRY.find(t => t.name === name);
if (!tool) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: \`Unknown tool: \${name}\`,
available_tools: TOOL_REGISTRY.length,
}, null, 2),
},
],
isError: true,
};
}
try {
// Build URL with path parameters
let url = \`\${BACKEND_URL}\${tool.endpoint}\`;
// Replace path parameters
Object.entries(args).forEach(([key, value]) => {
url = url.replace(\`{\${key}}\`, value);
});
// Build query string for GET requests
if (tool.method === 'GET') {
const queryParams = new URLSearchParams();
Object.entries(args).forEach(([key, value]) => {
if (!tool.endpoint.includes(\`{\${key}}\`) && value !== undefined) {
queryParams.append(key, value);
}
});
const queryString = queryParams.toString();
if (queryString) {
url += \`?\${queryString}\`;
}
}
// Make request
const options = {
method: tool.method,
};
// Add body for POST/PUT/PATCH
if (['POST', 'PUT', 'PATCH'].includes(tool.method) && args.body) {
options.body = typeof args.body === 'string' ? args.body : JSON.stringify(args.body);
}
const result = await makeRequest(url, options);
return {
content: [
{
type: 'text',
text: JSON.stringify({
tool: tool.name,
category: tool.category,
endpoint: tool.endpoint,
method: tool.method,
url: url,
status: result.status,
ok: result.ok,
response: result.data,
}, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: error.message,
tool: tool.name,
stack: error.stack,
}, null, 2),
},
],
isError: true,
};
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Solid Comprehensive MCP Server running');
console.error(\`Total tools available: \${TOOL_REGISTRY.length}\`);
console.error(\`Backend: \${BACKEND_URL}\`);
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});
`;
// Write comprehensive server
const serverPath = path.join(__dirname, '..', 'src', 'comprehensive-index.js');
fs.writeFileSync(serverPath, serverCode);
console.log(`ā
Comprehensive server written to: ${serverPath}`);
// Update package.json scripts
const packagePath = path.join(__dirname, '..', 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
packageJson.scripts = packageJson.scripts || {};
packageJson.scripts['start:comprehensive'] = 'node src/comprehensive-index.js';
packageJson.scripts['generate'] = 'node scripts/generate-tools.js';
packageJson.scripts['build'] = 'node scripts/build-mcp-server.js';
packageJson.scripts['rebuild'] = 'npm run generate && npm run build';
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
console.log(`ā
Updated package.json scripts`);
// Generate statistics
const stats = {
total_tools: tools.length,
by_category: Object.entries(categories).map(([name, tools]) => ({
name,
count: tools.length,
})).sort((a, b) => b.count - a.count),
by_method: {},
};
tools.forEach(tool => {
stats.by_method[tool.method] = (stats.by_method[tool.method] || 0) + 1;
});
console.log(`\nš Statistics:`);
console.log(` Total tools: ${stats.total_tools}`);
console.log(` Categories: ${stats.by_category.length}`);
console.log(` Methods:`, stats.by_method);
console.log(`\n Top 5 categories:`);
stats.by_category.slice(0, 5).forEach(cat => {
console.log(` - ${cat.name}: ${cat.count} tools`);
});
return { tools, categories, stats };
}
// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
buildServer()
.then(({ stats }) => {
console.log('\nš MCP server build complete!');
console.log(`\nTo use the comprehensive server:`);
console.log(`1. Test: echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | node src/comprehensive-index.js`);
console.log(`2. Update Claude Desktop config to use: src/comprehensive-index.js`);
console.log(`3. Enjoy access to ALL ${stats.total_tools} tools!`);
})
.catch(err => {
console.error('Fatal error:', err);
process.exit(1);
});
}
export { buildServer };