quality-mcp
Version:
An MCP server that analyzes to your codebase, with plugin support for DCD and Simian. 🏍️ "The only Zen you find on the tops of mountains is the Zen you bring up there."
277 lines (231 loc) • 7.59 kB
JavaScript
/**
* Quality MCP Server
* Main entry point for the Model Context Protocol server that integrates
* Quality code analysis with AI development workflows.
*/
import { spawn } from 'node:child_process';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { PluginManager } from './core/plugin-manager.js';
import { SimianPlugin } from './plugins/simian/index.js';
import { DCDPlugin } from './plugins/dcd/index.js';
import { createLogger } from './utils/logger.js';
import { loadConfig } from './utils/config.js';
const logger = createLogger('main');
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
/**
* Dependency injection for testability
* @returns {Object} Dependencies
*/
function getDeps() {
return {
Server,
QualityMCPServer,
StdioServerTransport,
PluginManager,
SimianPlugin,
DCDPlugin,
loadConfig,
logger,
};
}
/**
* Main server class that orchestrates the MCP server and plugin system
*/
class QualityMCPServer {
constructor(_getDeps = getDeps) {
const { Server, PluginManager } = _getDeps();
this.server = new Server(
{
name: 'quality-mcp-server',
version: '0.1.0',
},
{
capabilities: {
tools: {},
resources: {},
prompts: {},
},
}
);
this.pluginManager = new PluginManager(this.server);
this.config = null;
}
async initialize(_getDeps = getDeps) {
const { loadConfig, logger } = _getDeps();
logger.info('Initializing Quality MCP Server...');
try {
// Load configuration
this.config = await loadConfig();
logger.info('Configuration loaded successfully');
// Register core plugins
await this.registerPlugins();
// Set up error handling
this.setupErrorHandling();
logger.info('Server initialized successfully');
} catch (error) {
logger.error('Failed to initialize server:', error);
throw error;
}
}
async registerPlugins(_getDeps = getDeps) {
const { DCDPlugin, SimianPlugin, logger } = _getDeps();
logger.info('Registering plugins...');
try {
// Register DCD plugin as the primary analysis tool (open source alternative)
const dcdPlugin = new DCDPlugin(this.config.plugins.dcd || this.config.plugins.simian);
await this.pluginManager.register('dcd', dcdPlugin);
// Register Simian plugin as secondary option (requires commercial license)
const simianPlugin = new SimianPlugin(this.config.plugins.simian);
await this.pluginManager.register('simian', simianPlugin);
logger.info('Plugins registered successfully');
} catch (error) {
logger.error('Failed to register plugins:', error);
throw error;
}
}
setupErrorHandling(_getDeps = getDeps) {
const { logger } = _getDeps();
// Handle server errors
this.server.onerror = error => {
logger.error('Server error:', error);
};
// Handle plugin errors
this.pluginManager.on('error', (pluginName, error) => {
logger.error(`Plugin error in ${pluginName}:`, error);
});
// Handle process errors
global.process.on('uncaughtException', error => {
logger.error('Uncaught exception:', error);
global.process.exit(1);
});
global.process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled rejection at:', promise, 'reason:', reason);
global.process.exit(1);
});
}
async start(_getDeps = getDeps) {
const { StdioServerTransport, logger } = _getDeps();
logger.info('Starting Quality MCP Server...');
try {
// Create stdio transport
const transport = new StdioServerTransport();
// Add error handling for transport
transport.onerror = error => {
logger.error('Transport error:', error);
};
// Add error handling for server connection
this.server.onerror = error => {
logger.error('Server error during connection:', error);
};
// Connect server to transport with better error handling
await this.server.connect(transport);
logger.info('Server started successfully on stdio transport');
logger.info('Ready to receive MCP requests...');
} catch (error) {
logger.error('Failed to start server:', error);
throw error;
}
}
async shutdown(_getDeps = getDeps) {
const { logger } = _getDeps();
logger.info('Shutting down server...');
try {
await this.pluginManager.shutdown();
logger.info('Server shutdown complete');
} catch (error) {
logger.error('Error during shutdown:', error);
}
}
}
/**
* Main execution function
* @param {Function} _getDeps - Dependency injection
*/
async function main(_getDeps = getDeps) {
process.env.MCP_SERVER = 'true';
const { QualityMCPServer, logger } = _getDeps();
const server = new QualityMCPServer();
try {
await server.initialize();
await server.start();
// Handle graceful shutdown
global.process.on('SIGINT', async () => {
logger.info('Received SIGINT, shutting down...');
await server.shutdown();
global.process.exit(0);
});
global.process.on('SIGTERM', async () => {
logger.info('Received SIGTERM, shutting down...');
await server.shutdown();
global.process.exit(0);
});
} catch (error) {
logger.error('Failed to start server:', error);
global.process.exit(1);
}
}
function printUsage() {
console.log('quality-mcp');
console.log('');
console.log('Usage:');
console.log(' quality-mcp Start MCP server (stdio)');
console.log(' quality-mcp setup-dcd Install and verify DCD');
console.log(' quality-mcp setup Setup Simian (requires license)');
console.log(' quality-mcp --help Show this help');
}
function runScript(scriptName, args = []) {
const scriptPath = resolve(PACKAGE_ROOT, 'scripts', scriptName);
return new Promise((resolvePromise, rejectPromise) => {
const child = spawn('bash', [scriptPath, ...args], { stdio: 'inherit' });
child.on('error', error => {
rejectPromise(error);
});
child.on('exit', code => {
if (code === 0) {
resolvePromise();
return;
}
rejectPromise(new Error(`Script ${scriptName} exited with code ${code}`));
});
});
}
async function runCliCommand(command, args) {
switch (command) {
case 'setup-dcd':
await runScript('setup-dcd.sh', args);
return true;
case 'setup':
await runScript('setup-simian.sh', args);
return true;
case '-h':
case '--help':
printUsage();
return true;
default:
return false;
}
}
// Only run if this file is executed directly
if (import.meta.url === `file://${global.process.argv[1]}`) {
const [, , command, ...args] = global.process.argv;
runCliCommand(command, args)
.then(handled => {
if (!handled) {
// Set environment variable to indicate MCP server mode
process.env.MCP_SERVER = 'true';
return main().catch(error => {
console.error('Fatal error:', error);
global.process.exit(1);
});
}
})
.catch(error => {
console.error('Fatal error:', error);
global.process.exit(1);
});
}
export { QualityMCPServer, main, getDeps };