mcp-quiz-server
Version:
š§ AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.
288 lines (287 loc) ⢠10.2 kB
JavaScript
;
/**
* @moduleName: MCP Quiz Server - NPX CLI Tool
* @version: 2.0.0
* @since: 2025-07-23
* @lastUpdated: 2025-07-27
* @projectSummary: NPX-installable CLI tool for MCP Quiz Server with web server startup capability and MCP integration
* @techStack: TypeScript, Node.js CLI, Commander.js, Express, MCP Protocol
* @dependency: commander, express, child_process
* @interModuleDependency: ./server, ./mcp-server, ./mcp-handler-enhanced
* @requirementsTraceability:
* {@link Requirements.REQ_OPS_005} (NPX Tool Integration)
* {@link Requirements.REQ_OPS_006} (Command Line Interface)
* {@link Requirements.REQ_OPS_001} (Structured Application Logging)
* {@link Requirements.REQ_OPS_002} (Health Check Endpoints)
* {@link Requirements.REQ_OPS_003} (Deployment Automation)
* {@link Requirements.REQ_OPS_007} (Runtime Configuration Management)
* @briefDescription: CLI entry point for NPX tool supporting server start/stop, MCP mode, and tool integration. Enables `npx mcp-quiz-server` usage with VS Code integration
* @methods: startWebServer, startMCPServer, handleCLICommands, serverControl
* @contributors: Jorge Sequeira
* @examples:
* - npx mcp-quiz-server start
* - npx mcp-quiz-server mcp
* - npx mcp-quiz-server --help
* @vulnerabilitiesAssessment: Process isolation, port binding validation, graceful shutdown handling
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.listServers = listServers;
exports.startMCPServer = startMCPServer;
exports.startWebServer = startWebServer;
exports.stopServers = stopServers;
const child_process_1 = require("child_process");
const commander_1 = require("commander");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const program = new commander_1.Command();
let activeServers = [];
/**
* Start the web server
*/
async function startWebServer(options) {
const port = options.port || 3000;
console.log(`š Starting MCP Quiz Server on port ${port}...`);
try {
const serverPath = path.join(__dirname, 'server');
if (!fs.existsSync(serverPath)) {
console.error('ā Server build not found. Run: npm run build');
process.exit(1);
}
const serverProcess = (0, child_process_1.spawn)('node', [serverPath], {
env: { ...process.env, PORT: port.toString() },
stdio: options.background ? 'pipe' : 'inherit',
});
if (options.background) {
console.log(`ā
Web server started in background (PID: ${serverProcess.pid})`);
console.log(`š Access at: http://localhost:${port}`);
console.log(`š± API docs: http://localhost:${port}/health`);
activeServers.push({
process: serverProcess,
port,
type: 'web',
});
return {
pid: serverProcess.pid,
port,
url: `http://localhost:${port}`,
};
}
else {
console.log(`š Web server starting at: http://localhost:${port}`);
console.log('Press Ctrl+C to stop...');
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\nš Shutting down web server...');
serverProcess.kill('SIGTERM');
process.exit(0);
});
serverProcess.on('close', code => {
console.log(`\nš Web server exited with code ${code}`);
process.exit(code || 0);
});
}
}
catch (error) {
console.error('ā Failed to start web server:', error);
process.exit(1);
}
}
/**
* Start the MCP server (STDIO mode)
*/
async function startMCPServer() {
console.log('š¤ Starting MCP Server (STDIO mode)...');
try {
const mcpServerPath = path.join(__dirname, 'mcp-server');
if (!fs.existsSync(mcpServerPath)) {
console.error('ā MCP server build not found. Run: npm run build');
process.exit(1);
}
const mcpProcess = (0, child_process_1.spawn)('node', [mcpServerPath], {
stdio: 'inherit',
});
console.log('š” MCP Server ready for JSON-RPC 2.0 communication');
console.log('š Use in VS Code: Add to .vscode/mcp.json');
console.log('Press Ctrl+C to stop...');
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\nš Shutting down MCP server...');
mcpProcess.kill('SIGTERM');
process.exit(0);
});
mcpProcess.on('close', code => {
console.log(`\nš MCP server exited with code ${code}`);
process.exit(code || 0);
});
}
catch (error) {
console.error('ā Failed to start MCP server:', error);
process.exit(1);
}
}
/**
* Stop all running servers
*/
function stopServers() {
console.log('š Stopping all active servers...');
activeServers.forEach((server, index) => {
console.log(` Stopping ${server.type} server on port ${server.port}...`);
server.process.kill('SIGTERM');
});
activeServers = [];
console.log('ā
All servers stopped');
}
/**
* List running servers
*/
function listServers() {
if (activeServers.length === 0) {
console.log('š No active servers');
return;
}
console.log('š Active Servers:');
activeServers.forEach((server, index) => {
console.log(` ${index + 1}. ${server.type} server - Port ${server.port} (PID: ${server.process.pid})`);
});
}
/**
* Show VS Code integration instructions
*/
function showVSCodeIntegration() {
const currentDir = process.cwd();
const mcpPath = path.join(currentDir, 'node_modules', 'mcp-quiz-server', 'dist', 'mcp-server');
console.log('š§ VS Code MCP Integration Setup:');
console.log('');
console.log('1. Add to your .vscode/mcp.json:');
console.log('');
console.log(JSON.stringify({
servers: {
'quiz-server': {
type: 'stdio',
command: 'npx',
args: ['mcp-quiz-server', 'mcp'],
env: {
NODE_ENV: 'production',
},
},
},
}, null, 2));
console.log('');
console.log('2. Restart VS Code');
console.log('3. The MCP Quiz Server will be available in VS Code chat');
console.log('');
console.log('š ļø Alternative (if NPX fails):');
console.log(` "command": "node"`);
console.log(` "args": ["${mcpPath}"]`);
}
// CLI Command Definitions
program
.name('mcp-quiz-server')
.description('MCP Quiz Server - NPX CLI Tool for AI-powered quiz management')
.version('2.0.0');
program
.command('start')
.description('Start the web server')
.option('-p, --port <number>', 'port number', '3000')
.option('-b, --background', 'run in background')
.action(async (options) => {
await startWebServer({
port: parseInt(options.port),
background: options.background,
});
});
program
.command('mcp')
.description('Start the MCP server (STDIO mode for VS Code)')
.action(async () => {
await startMCPServer();
});
program
.command('stop')
.description('Stop all running servers')
.action(() => {
stopServers();
});
program
.command('list')
.description('List active servers')
.action(() => {
listServers();
});
program
.command('vscode')
.description('Show VS Code MCP integration instructions')
.action(() => {
showVSCodeIntegration();
});
program
.command('dev')
.description('Start development mode (web server + auto-reload)')
.option('-p, --port <number>', 'port number', '3000')
.action(async (options) => {
console.log('š„ Starting development mode...');
const devProcess = (0, child_process_1.spawn)('npm', ['run', 'dev'], {
env: { ...process.env, PORT: options.port },
stdio: 'inherit',
});
console.log('š§ Development server with auto-reload enabled');
console.log('Press Ctrl+C to stop...');
process.on('SIGINT', () => {
console.log('\nš Stopping development server...');
devProcess.kill('SIGTERM');
process.exit(0);
});
devProcess.on('close', code => {
console.log(`\nš Development server exited with code ${code}`);
process.exit(code || 0);
});
});
// Handle no command (show help)
if (process.argv.length <= 2) {
program.help();
}
// Parse CLI arguments
program.parse();
// Handle graceful shutdown
process.on('SIGINT', () => {
stopServers();
process.exit(0);
});
process.on('SIGTERM', () => {
stopServers();
process.exit(0);
});