|
@@ -1,6 +1,7 @@
|
|
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
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 { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
|
|
|
+import type { Express, Request, Response } from 'express';
|
|
|
import { logger } from '../utils/logger';
|
|
import { logger } from '../utils/logger';
|
|
|
import { ShopDatabase } from '../database/Database';
|
|
import { ShopDatabase } from '../database/Database';
|
|
|
import { QdrantService } from '../services/QdrantService';
|
|
import { QdrantService } from '../services/QdrantService';
|
|
@@ -14,6 +15,7 @@ import type { AppConfig } from '../config';
|
|
|
|
|
|
|
|
export class McpServer {
|
|
export class McpServer {
|
|
|
private server!: Server;
|
|
private server!: Server;
|
|
|
|
|
+ private transport!: StreamableHTTPServerTransport;
|
|
|
private database!: ShopDatabase;
|
|
private database!: ShopDatabase;
|
|
|
private qdrantService!: QdrantService;
|
|
private qdrantService!: QdrantService;
|
|
|
private embeddingService!: EmbeddingService;
|
|
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) {
|
|
if (!this.enabled) {
|
|
|
- logger.info('MCP Server start skipped - disabled by configuration');
|
|
|
|
|
|
|
+ logger.info('MCP routes not registered - disabled by configuration');
|
|
|
return;
|
|
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)');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|