| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- /**
- * HTTP Server for Gogs MCP
- * Provides HTTP/SSE transport for the MCP server
- */
- import express from 'express';
- import cors from 'cors';
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
- import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
- export interface HttpServerOptions {
- port: number;
- host: string;
- server: Server;
- }
- export class HttpMcpServer {
- private app: express.Application;
- private server: Server;
- private httpServer: any; // HTTP server instance from Express
- private port: number;
- private host: string;
- private transports: Map<string, SSEServerTransport> = new Map();
- constructor(options: HttpServerOptions) {
- this.server = options.server;
- this.port = options.port;
- this.host = options.host;
- this.app = express();
- this.setupMiddleware();
- this.setupRoutes();
- }
- private setupMiddleware() {
- this.app.use(cors());
- this.app.use(express.json());
- }
- private setupRoutes() {
- // Health check endpoint
- this.app.get('/health', (_req, res) => {
- res.json({
- status: 'ok',
- service: 'gogs-mcp-server',
- transport: 'http',
- });
- });
- // SSE endpoint for MCP protocol
- this.app.get('/sse', async (req, res) => {
- console.error('New SSE connection established');
- const transport = new SSEServerTransport('/messages', res);
- // Store transport by session ID
- this.transports.set(transport.sessionId, transport);
- console.error(`Transport stored with session ID: ${transport.sessionId}`);
- // Clean up when connection closes
- res.on('close', () => {
- console.error(`SSE connection closed for session: ${transport.sessionId}`);
- this.transports.delete(transport.sessionId);
- });
- await this.server.connect(transport);
- });
- // Message endpoint for client requests (must match the path given to SSEServerTransport)
- this.app.post('/messages', async (req, res) => {
- const sessionId = req.query.sessionId as string;
- if (!sessionId) {
- res.status(400).json({
- jsonrpc: '2.0',
- error: {
- code: -32000,
- message: 'Bad Request: No sessionId provided'
- },
- id: null
- });
- return;
- }
- const transport = this.transports.get(sessionId);
- if (!transport) {
- res.status(400).json({
- jsonrpc: '2.0',
- error: {
- code: -32000,
- message: 'Bad Request: No transport found for sessionId'
- },
- id: null
- });
- return;
- }
- try {
- await transport.handlePostMessage(req, res, req.body);
- } catch (error) {
- console.error('Error handling message:', error);
- if (!res.headersSent) {
- res.status(500).json({
- jsonrpc: '2.0',
- error: {
- code: -32603,
- message: error instanceof Error ? error.message : 'Internal server error'
- },
- id: null
- });
- }
- }
- });
- }
- public start(): Promise<void> {
- return new Promise((resolve, reject) => {
- try {
- this.httpServer = this.app.listen(this.port, this.host, () => {
- console.error(`Gogs MCP HTTP Server running on http://${this.host}:${this.port}`);
- console.error(`SSE endpoint: http://${this.host}:${this.port}/sse`);
- console.error(`Messages endpoint: http://${this.host}:${this.port}/messages`);
- console.error(`Health check: http://${this.host}:${this.port}/health`);
- resolve();
- });
- this.httpServer.on('error', (error: Error) => {
- console.error('HTTP server error:', error);
- reject(error);
- });
- } catch (error) {
- console.error('Failed to start HTTP server:', error);
- reject(error);
- }
- });
- }
- public async cleanup(): Promise<void> {
- console.error('Cleaning up HTTP server transports...');
- for (const [sessionId, transport] of this.transports) {
- try {
- console.error(`Closing transport for session ${sessionId}`);
- await transport.close();
- } catch (error) {
- console.error(`Error closing transport for session ${sessionId}:`, error);
- }
- }
- this.transports.clear();
- // Close HTTP server
- if (this.httpServer) {
- return new Promise((resolve) => {
- this.httpServer.close(() => {
- console.error('HTTP server closed');
- resolve();
- });
- });
- }
- }
- }
|