url-builder-mcp
Version:
URL Builder MCP Server - Professional URL construction with intelligent parameter handling for Claude Desktop
236 lines (194 loc) ⢠7.39 kB
JavaScript
/**
* URL Builder MCP Server - NPX Launcher
*
* Intelligent launcher that detects environment and runs the URL Builder MCP server
* with optimal configuration for Claude Desktop integration.
*/
import { spawn } from 'child_process';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { fileURLToPath } from 'url';
// ES module compatibility
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// ANSI color codes for beautiful output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m'
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
function findProjectRoot() {
let currentDir = __dirname;
// Go up from bin/ to project root
const projectRoot = path.dirname(currentDir);
// Verify it's the correct project by checking for dist/index.js or src/index.ts
const distFile = path.join(projectRoot, 'dist', 'index.js');
const srcFile = path.join(projectRoot, 'src', 'index.ts');
if (fs.existsSync(distFile) || fs.existsSync(srcFile)) {
return projectRoot;
}
// If not found, try current working directory
const cwd = process.cwd();
const cwdDistFile = path.join(cwd, 'dist', 'index.js');
const cwdSrcFile = path.join(cwd, 'src', 'index.ts');
if (fs.existsSync(cwdDistFile) || fs.existsSync(cwdSrcFile)) {
return cwd;
}
throw new Error('URL Builder MCP server files not found. Please run from project directory.');
}
function checkEnvironment(projectRoot) {
const distFile = path.join(projectRoot, 'dist', 'index.js');
const srcFile = path.join(projectRoot, 'src', 'index.ts');
const packageFile = path.join(projectRoot, 'package.json');
const nodeModules = path.join(projectRoot, 'node_modules');
if (fs.existsSync(distFile)) {
return 'production';
} else if (fs.existsSync(srcFile) && fs.existsSync(nodeModules)) {
return 'development';
} else if (fs.existsSync(srcFile)) {
return 'source';
}
return 'unknown';
}
function buildCommand(projectRoot, environment) {
switch (environment) {
case 'production':
return {
command: 'node',
args: [path.join(projectRoot, 'dist', 'index.js')],
cwd: projectRoot
};
case 'development':
// Check if ts-node is available
const tsNodePath = path.join(projectRoot, 'node_modules', '.bin', 'ts-node');
const isWindows = os.platform() === 'win32';
const tsNodeBin = isWindows ? `${tsNodePath}.cmd` : tsNodePath;
if (fs.existsSync(tsNodeBin)) {
return {
command: tsNodeBin,
args: [path.join(projectRoot, 'src', 'index.ts')],
cwd: projectRoot
};
}
// Fall through to source
case 'source':
return {
command: 'npx',
args: ['ts-node', path.join(projectRoot, 'src', 'index.ts')],
cwd: projectRoot
};
default:
throw new Error('Unable to determine how to run the URL Builder MCP server');
}
}
function loadEnvironment(projectRoot) {
const envFile = path.join(projectRoot, '.env');
const env = { ...process.env };
if (fs.existsSync(envFile)) {
const envContent = fs.readFileSync(envFile, 'utf8');
const envLines = envContent.split('\n');
for (const line of envLines) {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#')) {
const [key, ...valueParts] = trimmed.split('=');
if (key && valueParts.length > 0) {
env[key.trim()] = valueParts.join('=').trim();
}
}
}
}
return env;
}
function displayHeader() {
log('š URL Builder MCP Server', 'cyan');
log('ā'.repeat(50), 'blue');
log('š Starting intelligent MCP server for Claude Desktop', 'green');
log('');
}
function displayEnvironmentInfo(projectRoot, environment) {
log('š Environment Detection:', 'yellow');
log(` š Project Root: ${projectRoot}`, 'reset');
log(` š§ Environment: ${environment}`, 'reset');
log(` š» Platform: ${os.platform()} ${os.arch()}`, 'reset');
log('');
}
async function main() {
try {
// Handle help flag
if (process.argv.includes('--help') || process.argv.includes('-h')) {
displayHeader();
log('š Usage:', 'yellow');
log(' npx url-builder-mcp # Start the MCP server', 'reset');
log(' npx url-builder-mcp --help # Show this help', 'reset');
log('', 'reset');
log('š Documentation: https://github.com/enpetrache/url_builder_mcp#readme', 'reset');
return;
}
displayHeader();
// Find project root and detect environment
const projectRoot = findProjectRoot();
const environment = checkEnvironment(projectRoot);
displayEnvironmentInfo(projectRoot, environment);
// Build command
const { command, args, cwd } = buildCommand(projectRoot, environment);
// Load environment variables
const env = loadEnvironment(projectRoot);
log('šÆ Starting server...', 'green');
log(` Command: ${command} ${args.join(' ')}`, 'reset');
log('');
// Spawn the process
const child = spawn(command, args, {
cwd,
env,
stdio: 'inherit'
});
// Handle process events
child.on('error', (error) => {
log(`ā Failed to start server: ${error.message}`, 'red');
process.exit(1);
});
child.on('exit', (code, signal) => {
if (signal) {
log(`\nš Server stopped by signal: ${signal}`, 'yellow');
} else if (code !== 0) {
log(`\nā Server exited with code: ${code}`, 'red');
process.exit(code);
} else {
log('\nš Server stopped gracefully', 'green');
}
});
// Handle Ctrl+C gracefully
process.on('SIGINT', () => {
log('\nš Received SIGINT, stopping server...', 'yellow');
child.kill('SIGINT');
});
process.on('SIGTERM', () => {
log('\nš Received SIGTERM, stopping server...', 'yellow');
child.kill('SIGTERM');
});
} catch (error) {
log(`ā Error: ${error.message}`, 'red');
log('', 'reset');
log('š” Troubleshooting:', 'yellow');
log(' 1. Ensure you have Node.js 18+ installed', 'reset');
log(' 2. Run from the url-builder-mcp project directory', 'reset');
log(' 3. Build the project: npm run build', 'reset');
log(' 4. Install dependencies: npm install', 'reset');
process.exit(1);
}
}
// Run the launcher
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
export { main };