Newer
Older
TelosDB / src / backend / mcp-server.js
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
import { CONFIG } from "./config.js";
import { initDb } from "./db.js";
import { Logger } from "./logger.js";
import { registerMcpHandlers } from "./mcp-handlers.js";

const app = express();
let transport = null;
let httpServer = null;

/**
 * 初期化処理を実行
 */
try {
  initDb();
  Logger.info('Database initialized');
} catch (error) {
  Logger.error('Failed to initialize database', error);
  process.exit(1);
}

/**
 * MCP サーバーを作成
 */
const server = new Server(
  {
    name: CONFIG.mcp.name,
    version: CONFIG.mcp.version,
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

registerMcpHandlers(server);

/**
 * SSE トランスポートエラーハンドラ
 * @param {Error} error - エラーオブジェクト
 */
function handleTransportError(error) {
  Logger.error('Transport error', error);
  transport = null;
}

/**
 * MCP SSE サーバーを起動
 * @param {number} port - ポート番号
 * @returns {Object} HTTPサーバーオブジェクト
 * @throws {Error} サーバー起動に失敗した場合
 */
export function startMcpServer(port = 3000) {
  try {
    // ポート番号の検証
    if (!Number.isInteger(port) || port < 1 || port > 65535) {
      throw new Error(`Invalid port number: ${port}`);
    }

    app.use(express.json());

    /**
     * SSE 接続エンドポイント
     * @route GET /sse
     */
    app.get(CONFIG.mcp.ssePath, async (req, res) => {
      try {
        Logger.info('SSE connection established');
        transport = new SSEServerTransport(CONFIG.mcp.messagesPath, res);
        transport.on('error', handleTransportError);
        await server.connect(transport);
      } catch (error) {
        Logger.error('Failed to establish SSE connection', error);
        res.status(500).send('Failed to establish SSE connection');
      }
    });

    /**
     * メッセージハンドラエンドポイント
     * @route POST /messages
     */
    app.post(CONFIG.mcp.messagesPath, async (req, res) => {
      try {
        if (!transport) {
          Logger.warn('No active transport connection');
          return res.status(400).send('No transport connection');
        }
        await transport.handlePostMessage(req, res);
      } catch (error) {
        Logger.error('Failed to handle post message', error);
        res.status(500).send('Failed to handle message');
      }
    });

    /**
     * ヘルスチェックエンドポイント
     * @route GET /health
     */
    app.get('/health', (req, res) => {
      res.status(200).json({
        status: 'ok',
        timestamp: new Date().toISOString(),
        transport: transport ? 'connected' : 'disconnected',
      });
    });

    httpServer = app.listen(port, () => {
      Logger.info(`MCP SSE Server listening on http://localhost:${port}${CONFIG.mcp.ssePath}`);
    });

    // エラーハンドリング
    httpServer.on('error', (error) => {
      Logger.error('HTTP server error', error);
    });

    return httpServer;
  } catch (error) {
    Logger.error('Failed to start MCP server', error);
    throw error;
  }
}