Skip to main content
Glama

News Aggregator API

server.ts5.9 kB
/** * MCP Server setup for News Aggregator API */ // Load environment variables before any other imports import dotenv from 'dotenv'; dotenv.config(); import express, { Express, Request, Response, NextFunction } from 'express'; import cors from 'cors'; import helmet from 'helmet'; import swaggerUi from 'swagger-ui-express'; import path from 'path'; import fs from 'fs'; import routes from './routes'; import { logger } from './utils/logger'; import { cacheService } from './utils/cache'; import { connectDb, disconnectDb } from './utils/db'; import { swaggerSpec } from './utils/swagger'; export class McpServer { private app: Express; private port: number; public server: any; // HTTP server instance constructor(port: number = 3000) { this.app = express(); this.port = port; this.configureMiddleware(); this.configureRoutes(); this.configureErrorHandling(); } private configureMiddleware(): void { // Security middleware this.app.use(helmet()); // CORS setup this.app.use(cors()); // Parse JSON bodies this.app.use(express.json()); // Parse URL-encoded bodies this.app.use(express.urlencoded({ extended: true })); // Request logging this.app.use((req: Request, _res: Response, next: NextFunction) => { logger.info(`${req.method} ${req.url}`); next(); }); } private configureRoutes(): void { // API routes this.app.use('/api', routes); // API documentation endpoints this.app.use('/docs', swaggerUi.serve); this.app.get('/docs', swaggerUi.setup(swaggerSpec, { explorer: true, customCss: '.swagger-ui .topbar { display: none }', customSiteTitle: 'News Aggregator API Documentation', })); // OpenAPI specification JSON endpoint this.app.get('/docs.json', (_req: Request, res: Response) => { res.setHeader('Content-Type', 'application/json'); res.send(swaggerSpec); }); // Documentation examples endpoint this.app.get('/examples', (_req: Request, res: Response) => { const examplesPath = path.join(__dirname, 'docs', 'examples.md'); fs.readFile(examplesPath, 'utf8', (err: NodeJS.ErrnoException | null, data: string) => { if (err) { logger.error('Error reading examples file:', err); return res.status(500).json({ success: false, error: 'Failed to load examples documentation' }); } res.setHeader('Content-Type', 'text/markdown'); res.send(data); }); }); // Health check endpoint this.app.get('/health', (_req: Request, res: Response) => { res.status(200).json({ status: 'ok' }); }); // Handle 404s this.app.use((_req: Request, res: Response) => { res.status(404).json({ success: false, error: 'Resource not found' }); }); } private configureErrorHandling(): void { // Global error handler this.app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { logger.error('Unhandled error:', err); res.status(500).json({ success: false, error: process.env.NODE_ENV === 'production' ? 'An unexpected error occurred' : err.message }); }); } public async start(): Promise<void> { try { // Connect to the database await connectDb(); const port = process.env.PORT || 3000; this.server = this.app.listen(port, () => { logger.info(`Server is running on port ${port}`); }); // Set up graceful shutdown process.on('SIGTERM', () => this.shutdown()); process.on('SIGINT', () => this.shutdown()); } catch (error) { logger.error('Failed to start server:', error); process.exit(1); } } public async shutdown(): Promise<void> { logger.info('Shutting down server...'); // Properly wait for the server to close using a Promise if (this.server) { // First, forcefully close all existing connections to prevent hanging // This is especially important for tests where connections might still be active if (this.server.closeAllConnections && typeof this.server.closeAllConnections === 'function') { try { this.server.closeAllConnections(); logger.info('Forcibly closed all existing connections'); } catch (error) { logger.warn('Failed to close all connections:', error); } } // Now wait for the server to fully close await new Promise<void>((resolve) => { this.server.close((err: Error | undefined) => { if (err) { logger.warn('Error during server close:', err); } else { logger.info('HTTP server closed successfully'); } resolve(); // Resolve regardless of error to prevent hanging }); }); this.server = null; // Clear the server reference } // Disconnect from database await disconnectDb(); logger.info('Server shutdown complete'); // Only exit process in non-test environments if (process.env.NODE_ENV !== 'test') { process.exit(0); } } /** * Get the Express app instance for testing purposes */ public getApp(): Express { return this.app; } } // Create and export instances for tests const mcpServer = new McpServer(); const app = mcpServer.getApp(); // Export the HTTP server for testing import http from 'http'; export const server = http.createServer(app); // Export the app for testing export { app }; // Allow direct execution of server if (require.main === module) { const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; mcpServer.start().then(() => { // When running directly, mcpServer will create its own HTTP server }); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Malachi-devel/the-news-api-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server