UNPKG

@the_cfdude/productboard-mcp

Version:

Model Context Protocol server for Productboard REST API with dynamic tool loading

131 lines (130 loc) 5.63 kB
/** * Main ProductboardServer class following session management pattern from Jira MCP analysis */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { setupToolHandlers } from './tools/index.js'; import { setupDynamicToolHandlers } from './tools/index-dynamic.js'; import { setupDocumentation } from './documentation/documentation-provider.js'; import { sessionManager } from './session-manager.js'; import { existsSync } from 'fs'; import { join } from 'path'; import { randomBytes } from 'crypto'; import { debugLog } from './utils/debug-logger.js'; export class ProductboardServer { server; initialized = false; currentSession = null; constructor() { debugLog('productboard-server', 'Initializing ProductBoard MCP Server'); this.server = new Server({ name: 'productboard-server', version: '0.1.0', }, { capabilities: { tools: {}, resources: {}, prompts: {}, }, }); // Setup error handling with session context this.server.onerror = error => { debugLog('productboard-server', 'MCP Server Error', { sessionId: this.currentSession?.sessionId, error: error.message, stack: error.stack, }); console.error('[MCP Error]', error); }; // Note: Removed global process handlers to avoid conflicts with Claude's process management // Session cleanup is handled by transport close events debugLog('productboard-server', 'ProductBoard MCP Server initialization completed'); } /** * Initialize the server with tool handlers */ async initialize(session) { if (this.initialized) return; debugLog('productboard-server', 'Initializing server with session support', { sessionId: session?.sessionId, }); // Setup tool handlers - use dynamic loading if manifest exists const manifestPath = join(process.cwd(), 'generated', 'manifest.json'); if (existsSync(manifestPath)) { console.error('Using dynamic tool loading with manifest'); debugLog('productboard-server', 'Using dynamic tool loading'); await setupDynamicToolHandlers(this.server, session); } else { console.error('Using static tool loading (no manifest found)'); debugLog('productboard-server', 'Using static tool loading'); setupToolHandlers(this.server, session); } // Setup documentation (prompts and resources) setupDocumentation(this.server); this.initialized = true; } /** * Start the server with session management */ async run() { debugLog('productboard-server', 'Starting ProductBoard MCP server transport'); const transport = new StdioServerTransport(); // Create a session for this STDIO connection with cryptographically secure random ID const randomSuffix = randomBytes(8).toString('hex'); const sessionId = `pb-stdio-${Date.now()}-${randomSuffix}`; this.currentSession = sessionManager.createSession(sessionId); debugLog('productboard-server', 'Created session for STDIO connection', { sessionId: this.currentSession.sessionId, totalSessions: sessionManager.getActiveSessionCount(), }); // Add transport event logging with session context transport.onclose = () => { debugLog('productboard-server', 'MCP transport closed', { sessionId: this.currentSession?.sessionId, }); if (this.currentSession) { sessionManager.removeSession(this.currentSession.sessionId); debugLog('productboard-server', 'Session cleaned up on transport close', { sessionId: this.currentSession.sessionId, }); } }; transport.onerror = error => { debugLog('productboard-server', 'MCP transport error', { sessionId: this.currentSession?.sessionId, error: error.message, stack: error.stack, }); }; // Initialize with current session await this.initialize(this.currentSession); try { await this.server.connect(transport); console.error('ProductBoard MCP server running on stdio'); debugLog('productboard-server', 'ProductBoard MCP server running on stdio transport', { sessionId: this.currentSession.sessionId, activeSessions: sessionManager.getActiveSessionCount(), }); // Log server connection status debugLog('productboard-server', 'Server connection established', { sessionId: this.currentSession.sessionId, serverName: 'productboard-server', serverVersion: '0.1.0', }); } catch (error) { debugLog('productboard-server', 'Failed to connect MCP server transport', { sessionId: this.currentSession?.sessionId, error: error.message, stack: error.stack, }); // Clean up session on connection failure if (this.currentSession) { sessionManager.removeSession(this.currentSession.sessionId); } throw error; } } }