UNPKG

@sofianedjerbi/knowledge-tree-mcp

Version:

MCP server for hierarchical project knowledge management

196 lines (193 loc) 6.87 kB
/** * Main Knowledge Tree MCP Server class * Coordinates all server components and manages lifecycle */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { join, resolve } from "path"; import { dirname } from "path"; import { fileURLToPath } from "url"; import { MCPHandlers } from './MCPHandlers.js'; import { ServerContextImpl } from './ServerContext.js'; import { ensureLogsDirectory } from '../utils/index.js'; import { SERVER_DEFAULTS } from '../constants/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); /** * Main server class for Knowledge Tree MCP * Implements the facade pattern to coordinate all server components */ export class KnowledgeTreeServer { state; options; context; handlers; constructor(config) { // Process and validate configuration this.options = this.processConfig(config); // Initialize server state this.state = { mcpServer: new Server(MCPHandlers.getServerInfo(), MCPHandlers.getCapabilities()), wsClients: new Set(), }; // Create server context for tool handlers this.context = new ServerContextImpl(this.options, this.state.wsClients); // Set up MCP protocol handlers this.handlers = new MCPHandlers(this.state.mcpServer, this.context); // Ensure logs directory exists this.initializeLogsDirectory(); } /** * Process and validate server configuration */ processConfig(config) { // Use default docs directory if not provided const knowledgeRoot = config.knowledgeRoot || join(__dirname, "..", "..", SERVER_DEFAULTS.DOCS_DIR); // Resolve to absolute path const resolvedRoot = resolve(knowledgeRoot); return { knowledgeRoot: resolvedRoot, logsDir: join(resolvedRoot, SERVER_DEFAULTS.LOGS_SUBDIR), webPort: config.webPort, debug: config.debug || false }; } /** * Initialize logs directory */ async initializeLogsDirectory() { try { const created = await ensureLogsDirectory(this.options.logsDir); if (created && this.options.debug) { console.error("📁 Created logs directory for usage analytics"); console.error("💡 TIP: Add 'docs/logs/' to your .gitignore file to keep analytics private"); } } catch (error) { console.error("Failed to create logs directory:", error); } } /** * Start the MCP server */ async start() { const transport = new StdioServerTransport(); await this.state.mcpServer.connect(transport); this.state.startedAt = new Date(); console.error(`Knowledge Tree MCP server started`); console.error(`📁 Docs directory: ${this.options.knowledgeRoot}`); if (this.options.webPort) { await this.startWebServer(); } else { console.error(`💡 Tip: Use --port <number> to enable web interface`); } } /** * Start the web interface server */ async startWebServer() { try { // Dynamically import web server module to avoid loading unnecessary dependencies const { WebServer } = await import('../web/server.js'); const webServer = new WebServer({ port: this.options.webPort, knowledgeRoot: this.options.knowledgeRoot, wsClients: this.state.wsClients, context: this.context }); await webServer.start(); this.state.webServer = webServer; console.error(`🌐 Web interface available at: http://localhost:${this.options.webPort}`); } catch (error) { console.error(`Failed to start web server: ${error}`); console.error(`Continuing without web interface...`); } } /** * Stop the server gracefully */ async stop() { // Close web server if running if (this.state.webServer) { await this.state.webServer.stop(); } // Close all WebSocket connections for (const client of this.state.wsClients) { try { client.socket.close(); } catch (error) { // Ignore errors during shutdown } } this.state.wsClients.clear(); // Note: MCP server doesn't have a stop method console.error("Knowledge Tree MCP server stopped"); } /** * Get server statistics */ getStats() { return { uptime: this.state.startedAt ? Date.now() - this.state.startedAt.getTime() : 0, wsClients: this.state.wsClients.size, knowledgeRoot: this.options.knowledgeRoot, webPort: this.options.webPort }; } /** * Create server from command line arguments */ static fromArgs(args) { let docsPath; let webPort; let debug = false; // Simple argument parsing for (let i = 0; i < args.length; i++) { if ((args[i] === "--docs" || args[i] === "-d") && args[i + 1]) { docsPath = args[i + 1]; i++; // Skip next arg } else if ((args[i] === "--port" || args[i] === "-p") && args[i + 1]) { const portValue = parseInt(args[i + 1], 10); if (!isNaN(portValue) && portValue > 0 && portValue < 65536) { webPort = portValue; } else { console.error(`Invalid port number: ${args[i + 1]}. Port must be between 1 and 65535.`); } i++; // Skip next arg } else if (args[i] === "--debug") { debug = true; } else if (args[i] === "--help" || args[i] === "-h") { console.log(` Knowledge Tree MCP Server Usage: knowledge-tree-mcp [options] Options: --docs, -d <path> Path to documentation directory (default: ./docs) --port, -p <number> Port for web interface (optional) --debug Enable debug logging --help, -h Show this help message Examples: knowledge-tree-mcp knowledge-tree-mcp --docs /path/to/docs knowledge-tree-mcp --port 3000 knowledge-tree-mcp --docs ./my-docs --port 8080 --debug `); process.exit(0); } } return new KnowledgeTreeServer({ knowledgeRoot: docsPath, webPort, debug }); } } //# sourceMappingURL=KnowledgeTreeServer.js.map