From 460a73265ff236793465915b9f428b04bf11ed72 Mon Sep 17 00:00:00 2001 From: Andreas Grub Date: Tue, 28 Apr 2026 07:59:44 +0200 Subject: [PATCH] fix: respect SERVER_TRANSPORT env var instead of hardcoding stdio The server start() method was hardcoded to always use stdio transport, ignoring the SERVER_TRANSPORT, SERVER_HTTP_PORT, and SERVER_HTTP_HOST environment variables that were documented in the README. Changes: - Read transport mode from config.server.transport (which is populated from SERVER_TRANSPORT env var by ConfigLoader) - Support 'stdio', 'http', and 'both' transport modes - Add HTTP/SSE server with /sse, /messages, and /health endpoints - Properly stop HTTP server on shutdown Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/server.ts | 56 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 4 deletions(-) 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'); }