Просмотр исходного кода

feat: switch MCP server to HTTP Streamable transport

- Replace StdioServerTransport with StreamableHTTPServerTransport
- Add registerRoutes() method to integrate MCP with main Express server
- MCP endpoints now available at /mcp (POST/GET/DELETE) and /mcp/health
- Removes need for separate MCP_PORT since it uses main server port

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fszontagh 8 месяцев назад
Родитель
Сommit
6781e9c4b3
2 измененных файлов с 73 добавлено и 13 удалено
  1. 7 0
      src/api/server.ts
  2. 66 13
      src/mcp/McpServer.ts

+ 7 - 0
src/api/server.ts

@@ -5,6 +5,7 @@ import { JobQueue } from '../queue/JobQueue';
 import { createRouter } from './routes';
 import { logger } from '../utils/logger';
 import { ShopDatabase } from '../database/Database';
+import { McpServer } from '../mcp/McpServer';
 import type { AppConfig } from '../config';
 
 export function createServer(apiKey: string, jobQueue: JobQueue, db?: ShopDatabase, config?: AppConfig): Express {
@@ -75,6 +76,12 @@ export function createServer(apiKey: string, jobQueue: JobQueue, db?: ShopDataba
   const webUIPath = path.join(__dirname, '../../dist/web');
   app.use('/ui', express.static(webUIPath));
 
+  // Register MCP routes if enabled (HTTP Streamable transport)
+  if (config?.mcp.enabled && config?.qdrant.enabled) {
+    const mcpServer = new McpServer(config);
+    mcpServer.registerRoutes(app);
+  }
+
   // API routes (authentication handled per endpoint based on documentation)
   const router = createRouter(jobQueue, apiKey, db, config);
   app.use('/', router);

+ 66 - 13
src/mcp/McpServer.ts

@@ -1,6 +1,7 @@
 import { Server } from '@modelcontextprotocol/sdk/server/index.js';
-import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
 import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
+import type { Express, Request, Response } from 'express';
 import { logger } from '../utils/logger';
 import { ShopDatabase } from '../database/Database';
 import { QdrantService } from '../services/QdrantService';
@@ -14,6 +15,7 @@ import type { AppConfig } from '../config';
 
 export class McpServer {
   private server!: Server;
+  private transport!: StreamableHTTPServerTransport;
   private database!: ShopDatabase;
   private qdrantService!: QdrantService;
   private embeddingService!: EmbeddingService;
@@ -252,27 +254,78 @@ export class McpServer {
   }
 
   /**
-   * Start the MCP server
+   * Register MCP routes on an Express app (HTTP Streamable transport)
    */
-  async start(): Promise<void> {
+  registerRoutes(app: Express): void {
     if (!this.enabled) {
-      logger.info('MCP Server start skipped - disabled by configuration');
+      logger.info('MCP routes not registered - disabled by configuration');
       return;
     }
 
-    try {
-      // Create transport (stdio for MCP)
-      const transport = new StdioServerTransport();
+    // Create StreamableHTTP transport (stateless mode)
+    this.transport = new StreamableHTTPServerTransport({
+      sessionIdGenerator: undefined,
+    });
 
-      // Connect server to transport
-      await this.server.connect(transport);
+    // Connect server to transport
+    this.server.connect(this.transport);
+
+    // POST - main MCP endpoint for requests
+    app.post('/mcp', async (req: Request, res: Response) => {
+      try {
+        await this.transport.handleRequest(req, res, req.body);
+      } catch (error) {
+        logger.error('MCP POST error:', error);
+        if (!res.headersSent) {
+          res.status(500).json({ error: 'MCP request failed' });
+        }
+      }
+    });
 
-      logger.info('MCP Server started successfully');
+    // GET - SSE endpoint for server-initiated messages
+    app.get('/mcp', async (req: Request, res: Response) => {
+      try {
+        await this.transport.handleRequest(req, res);
+      } catch (error) {
+        logger.error('MCP GET error:', error);
+        if (!res.headersSent) {
+          res.status(500).json({ error: 'MCP SSE failed' });
+        }
+      }
+    });
 
-    } catch (error) {
-      logger.error('Failed to start MCP Server:', error);
-      throw error;
+    // DELETE - session termination
+    app.delete('/mcp', async (req: Request, res: Response) => {
+      try {
+        await this.transport.handleRequest(req, res);
+      } catch (error) {
+        logger.error('MCP DELETE error:', error);
+        if (!res.headersSent) {
+          res.status(500).json({ error: 'MCP session termination failed' });
+        }
+      }
+    });
+
+    // Health check endpoint
+    app.get('/mcp/health', async (req: Request, res: Response) => {
+      const health = await this.healthCheck();
+      res.json(health);
+    });
+
+    logger.info('MCP HTTP routes registered at /mcp');
+  }
+
+  /**
+   * Start the MCP server (for HTTP mode, routes must be registered first)
+   */
+  async start(): Promise<void> {
+    if (!this.enabled) {
+      logger.info('MCP Server start skipped - disabled by configuration');
+      return;
     }
+
+    // Routes should already be registered via registerRoutes()
+    logger.info('MCP Server started (HTTP Streamable mode)');
   }
 
   /**