Skip to main content
Glama
index.ts10.4 kB
#!/usr/bin/env node 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 { Logger } from './utils/logger.js'; import { SafeErrorHandler } from './utils/safeErrorHandler.js'; import { ProjectContextManager } from './context/ProjectContextManager.js'; import { CommandRegistry } from './cli/CommandRegistry.js'; import { HealthChecker } from './components/HealthChecker.js'; import { StateManager } from './components/StateManager.js'; import { HealthEndpoint } from './components/HealthEndpoint.js'; import { MCPServerInitializer, SERVICE_DEPENDENCIES } from './initialization/MCPServerInitializer.js'; // Import tool schemas and handlers import { scanProjectDirsSchema, scanProjectDirs } from './tools/scanProjectDirs.js'; import { startDevServerSchema, startDevServer } from './tools/startDevServer.js'; import { getDevStatusSchema, getDevStatus } from './tools/getDevStatus.js'; import { getDevLogsSchema, getDevLogs } from './tools/getDevLogs.js'; import { stopDevServerSchema, stopDevServer } from './tools/stopDevServer.js'; import { restartDevServerSchema, restartDevServer } from './tools/restartDevServer.js'; import { getHealthStatusSchema, getHealthStatus } from './tools/getHealthStatus.js'; import { recoverFromStateSchema, recoverFromState } from './tools/recoverFromState.js'; import { autoRecoverSchema, autoRecover } from './tools/autoRecover.js'; // Initialize logger and safe error handler const logger = Logger.getInstance(); const safeErrorHandler = SafeErrorHandler.getInstance(); logger.setLogLevel('info'); // Setup safe error handlers (non-fatal error handling) safeErrorHandler.setupSafeErrorHandlers(); // Global initializer instance for MCP server let mcpInitializer: MCPServerInitializer | null = null; // Create server instance const server = new Server( { name: 'npm-dev-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Tool definitions const tools = [ scanProjectDirsSchema, startDevServerSchema, getDevStatusSchema, getDevLogsSchema, stopDevServerSchema, restartDevServerSchema, getHealthStatusSchema, recoverFromStateSchema, autoRecoverSchema, ]; // Handle list tools request server.setRequestHandler(ListToolsRequestSchema, async () => { logger.debug('Listing available tools'); return { tools, }; }); // Helper function for dependency checking async function handleDependencyCheck(toolName: string): Promise<any | null> { if (!mcpInitializer) { logger.warn(`MCP initializer not available for tool: ${toolName}`); return null; } if (!(toolName in SERVICE_DEPENDENCIES)) { return null; } try { await mcpInitializer.ensureToolDependencies(toolName as keyof typeof SERVICE_DEPENDENCIES); return null; } catch (dependencyError) { const errorMessage = dependencyError instanceof Error ? dependencyError.message : String(dependencyError); logger.warn(`Tool ${toolName} dependency not ready`, { error: errorMessage, tool: toolName }); return { content: [ { type: 'text', text: JSON.stringify({ success: false, message: `サービス初期化中のため一時的に利用できません: ${errorMessage}`, tool: toolName, retry: true, retryAfter: 5000 }), }, ], }; } } // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; logger.info(`Executing tool: ${name}`, { args }); try { // 依存関係チェック const dependencyError = await handleDependencyCheck(name); if (dependencyError) { return dependencyError; } switch (name) { case 'scan_project_dirs': return { content: [ { type: 'text', text: await scanProjectDirs(), }, ], }; case 'start_dev_server': return { content: [ { type: 'text', text: await startDevServer(args as { directory?: string }), }, ], }; case 'get_dev_status': return { content: [ { type: 'text', text: await getDevStatus(), }, ], }; case 'get_dev_logs': return { content: [ { type: 'text', text: await getDevLogs(args as { lines?: number }), }, ], }; case 'stop_dev_server': return { content: [ { type: 'text', text: await stopDevServer(), }, ], }; case 'restart_dev_server': return { content: [ { type: 'text', text: await restartDevServer(), }, ], }; case 'get_health_status': return { content: [ { type: 'text', text: await getHealthStatus(args as { detailed?: boolean }), }, ], }; case 'recover_from_state': return { content: [ { type: 'text', text: await recoverFromState(args as { force?: boolean }), }, ], }; case 'auto_recover': return { content: [ { type: 'text', text: await autoRecover(args as { maxRetries?: number; forceRecover?: boolean; restartMcp?: boolean }), }, ], }; default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { logger.error(`Tool execution failed: ${name}`, { error, args }); return { content: [ { type: 'text', text: JSON.stringify({ success: false, message: `ツール実行エラー: ${error}`, tool: name, error: String(error) }), }, ], }; } }); // Error handling server.onerror = (error) => { logger.error('Server error', { error }); }; process.on('SIGINT', async () => { logger.info('Received SIGINT, shutting down gracefully...'); // ヘルスチェックを停止 const healthChecker = HealthChecker.getInstance(); healthChecker.cleanup(); // 状態管理をクリーンアップ const stateManager = StateManager.getInstance(); stateManager.cleanup(); // ヘルスエンドポイントを停止 const healthEndpoint = HealthEndpoint.getInstance(); healthEndpoint.cleanup(); await server.close(); process.exit(0); }); process.on('SIGTERM', async () => { logger.info('Received SIGTERM, shutting down gracefully...'); // ヘルスチェックを停止 const healthChecker = HealthChecker.getInstance(); healthChecker.cleanup(); // 状態管理をクリーンアップ const stateManager = StateManager.getInstance(); stateManager.cleanup(); // ヘルスエンドポイントを停止 const healthEndpoint = HealthEndpoint.getInstance(); healthEndpoint.cleanup(); await server.close(); process.exit(0); }); // Determine run mode based on command line arguments function determineRunMode(): 'mcp' | 'cli' { // If no arguments or only stdio-related arguments, run as MCP server const args = process.argv.slice(2); // If executed via npx npm-dev-mcp with --mcp flag, run as MCP server if (args.includes('--mcp')) { return 'mcp'; } return args.length === 0 ? 'mcp' : 'cli'; } // Start MCP server mode async function startMCPServer() { try { // フェーズ1: 最小限初期化(STDIO接続のみ) const transport = new StdioServerTransport(); await server.connect(transport); logger.info('MCP JSON-RPC connection established'); logger.info('Available tools: ' + tools.map(t => t.name).join(', ')); // 初期化インスタンスを作成 mcpInitializer = new MCPServerInitializer(); // フェーズ2: 背景初期化(非同期実行) setImmediate(() => { mcpInitializer!.startBackgroundInitialization() .then(() => { logger.info('All background services initialized successfully'); }) .catch(error => { logger.error('Background service initialization failed', { error }); }); }); } catch (error) { logger.error('Failed to establish MCP connection', { error }); process.exit(1); } } // Start CLI mode async function startCLIMode() { try { const args = process.argv.slice(2); const commandRegistry = new CommandRegistry(); await commandRegistry.executeCommand(args); } catch (error) { logger.error('CLI execution failed', { error }); const errorMessage = error instanceof Error ? error.message : String(error); // Only output to console in CLI mode, not MCP mode if (determineRunMode() === 'cli') { console.error(`❌ ${errorMessage}`); } process.exit(1); } } // Main entry point async function main() { try { // Determine run mode first const runMode = determineRunMode(); // MCPモードの場合は早期にログを無効化 if (runMode === 'mcp') { logger.setMcpMode(true); } // Initialize project context const contextManager = ProjectContextManager.getInstance(); await contextManager.initialize(process.cwd()); // Execute run mode if (runMode === 'mcp') { await startMCPServer(); } else { await startCLIMode(); } } catch (error) { logger.error('Initialization failed', { error }); process.exit(1); } } // Error handlers are now managed by SafeErrorHandler // (setupSafeErrorHandlers() called above replaces the fatal handlers) main().catch((error) => { logger.error('Main function failed', { error }); safeErrorHandler.handleFatalError(error instanceof Error ? error : new Error(String(error)), 'main'); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/masamunet/npm-dev-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server