All files / src server.ts

7.81% Statements 5/64
0% Branches 0/6
0% Functions 0/7
7.81% Lines 5/64

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 1494x 4x 4x 4x         4x                                                                                                                                                                                                                                                                                        
import express, { Request, Response } from 'express';
import dotenv from 'dotenv';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { z } from 'zod'; // We'll need this for tool schemas
// import { z } from 'zod'; // We'll need this later for tool schemas
 
// --- Core Logic Modules ---
import { HistoryLogger } from './modules/historyLogger.js'; // Import the logger
import { CodebaseAnalyzer } from './modules/codebaseAnalyzer.js';
 
// Load environment variables from .env file
dotenv.config();
 
// --- Configuration ---
const PORT = process.env.PORT || 3001;
const SERVER_NAME = 'Lamplighter-MCP';
const SERVER_VERSION = '1.0.0'; // TODO: Read from package.json?
 
// --- MCP Server Setup ---
const server = new McpServer({
  name: SERVER_NAME,
  version: SERVER_VERSION,
});
 
// --- Core Logic Modules (Instantiation) ---
const historyLogger = new HistoryLogger(); // Instantiate the logger
const codebaseAnalyzer = new CodebaseAnalyzer();
// Instantiate other core logic modules here later
// e.g., const codebaseAnalyzer = new CodebaseAnalyzer();
 
// --- MCP Tools (Placeholders) ---
// Define your MCP tools here later using server.tool()
// We can add a simple test log call here if needed during dev
// historyLogger.log('Server starting up...'); // Example log
 
// --- MCP Tools ---
// Add analyze_codebase tool
server.tool(
  'analyze_codebase',
  'Analyzes the current codebase structure and generates a summary',
  {}, // Empty schema since we don't need any parameters
  async () => {
    try {
      await codebaseAnalyzer.analyze(process.cwd());
      await historyLogger.log('Codebase analysis completed successfully');
      return {
        content: [{ type: 'text', text: 'Codebase analysis completed. Check codebase_summary.md for results.' }]
      };
    } catch (error) {
      console.error('[analyze_codebase] Error:', error);
      return {
        content: [{ type: 'text', text: `Error analyzing codebase: ${error}` }],
        isError: true
      };
    }
  }
);
 
// --- Express App Setup ---
const app = express();
app.use(express.json()); // Middleware to parse JSON bodies
 
// --- Transport Management ---
// Store transports keyed by sessionId to handle multiple clients
const transports: { [sessionId: string]: SSEServerTransport } = {};
 
// --- SSE Endpoint --- 
// Client connects here to establish the MCP connection
app.get('/sse', async (req: Request, res: Response) => {
  console.log(`[${new Date().toISOString()}] Client connected via SSE`);
  
  // Use '/messages' as the endpoint for the client to POST messages back to
  const transport = new SSEServerTransport('/messages', res);
  transports[transport.sessionId] = transport;
 
  // Handle client disconnection
  req.on('close', () => {
    console.log(`[${new Date().toISOString()}] Client disconnected (Session ID: ${transport.sessionId})`);
    delete transports[transport.sessionId];
    transport.close(); // Ensure transport resources are cleaned up
  });
 
  try {
    // Connect the McpServer to this specific client's transport
    await server.connect(transport);
    console.log(`[${new Date().toISOString()}] MCP Server connected to transport (Session ID: ${transport.sessionId})`);
  } catch (error) {
    console.error(`[${new Date().toISOString()}] Error connecting MCP Server to transport (Session ID: ${transport.sessionId}):`, error);
    // Ensure response ends if connection fails
    Iif (!res.writableEnded) {
        res.status(500).end();
    }
     delete transports[transport.sessionId]; // Clean up failed connection
  }
});
 
// --- Messages Endpoint ---
// Client POSTs messages (like tool calls) to this endpoint
app.post('/messages', async (req: Request, res: Response) => {
  const sessionId = req.query.sessionId as string;
  const transport = transports[sessionId];
 
  if (transport) {
    try {
      console.log(`[${new Date().toISOString()}] Received message for Session ID: ${sessionId}`);
      await transport.handlePostMessage(req, res);
       console.log(`[${new Date().toISOString()}] Handled message for Session ID: ${sessionId}`);
    } catch (error) {
        console.error(`[${new Date().toISOString()}] Error handling POST message for Session ID: ${sessionId}:`, error);
        Iif (!res.headersSent) {
            res.status(500).send('Internal Server Error');
        }
    }
  } else {
    console.warn(`[${new Date().toISOString()}] No active transport found for Session ID: ${sessionId}`);
    res.status(400).send(`No active MCP session found for ID: ${sessionId}`);
  }
});
 
// --- Server Start ---
app.listen(PORT, async () => {
  console.log(`[${new Date().toISOString()}] ${SERVER_NAME} v${SERVER_VERSION} listening on port ${PORT}`);
  console.log(`[${new Date().toISOString()}] SSE endpoint available at http://localhost:${PORT}/sse`);
  console.log(`[${new Date().toISOString()}] Messages endpoint available at http://localhost:${PORT}/messages`);
 
  // Test the CodebaseAnalyzer
  try {
    console.log('[TEST] Running initial codebase analysis...');
    await codebaseAnalyzer.analyze(process.cwd());
    console.log('[TEST] Codebase analysis completed. Check lamplighter_context/codebase_summary.md');
  } catch (error) {
    console.error('[TEST] Error during codebase analysis:', error);
  }
});
 
// Basic error handling for uncaught exceptions
process.on('uncaughtException', (error) => {
  console.error(`[${new Date().toISOString()}] Uncaught Exception:`, error);
  // Optionally exit gracefully - consider PM2 or similar for production
  // process.exit(1);
});
 
process.on('unhandledRejection', (reason, promise) => {
  console.error(`[${new Date().toISOString()}] Unhandled Rejection at:`, promise, 'reason:', reason);
  // Optionally exit gracefully
  // process.exit(1);
});