diff --git a/src/server.ts b/src/server.ts index f7ac7a9..7cd0574 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,7 +5,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; +import { createServer, IncomingMessage, ServerResponse, Server } from 'http'; import { z } from 'zod'; import { CannyMCPConfig } from './types/config.js'; @@ -28,6 +30,8 @@ export class CannyMCPServer { private logger: Logger; private context: ToolContext; private prompts: MCPPrompt[]; + private httpServer?: Server; + private sseTransport?: SSEServerTransport; constructor(private config: CannyMCPConfig) { this.logger = new Logger(config.logging); @@ -279,15 +283,59 @@ export class CannyMCPServer { } async start(): Promise { - const transport = new StdioServerTransport(); - await this.connectTransport(transport); + const transportMode = this.config.server.transport || 'stdio'; + + if (transportMode === 'stdio' || transportMode === 'both') { + const transport = new StdioServerTransport(); + await this.connectTransport(transport); + this.logger.info('Canny MCP Server started', { + transport: 'stdio', + }); + } + + if (transportMode === 'http' || transportMode === 'both') { + await this.startHttpServer(); + } + } + + private async startHttpServer(): Promise { + const port = this.config.server.http?.port ?? 3000; + const host = this.config.server.http?.host ?? '0.0.0.0'; + + this.httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => { + if (req.method === 'GET' && req.url === '/sse') { + this.sseTransport = new SSEServerTransport('/messages', res); + await this.server.connect(this.sseTransport); + this.logger.info('SSE client connected'); + } else if (req.method === 'POST' && req.url === '/messages') { + if (this.sseTransport) { + await this.sseTransport.handlePostMessage(req, res); + } else { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'No SSE connection established' })); + } + } else if (req.method === 'GET' && req.url === '/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'ok' })); + } else { + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Not found' })); + } + }); - this.logger.info('Canny MCP Server started', { - transport: 'stdio', + this.httpServer.listen(port, host, () => { + this.logger.info('Canny MCP Server HTTP started', { + transport: 'http', + port, + host, + }); }); } async stop(): Promise { + if (this.httpServer) { + await new Promise((resolve) => this.httpServer!.close(() => resolve())); + } await this.server.close(); this.logger.info('Canny MCP Server stopped'); }