Skip to main content
Glama
jedarden

YouTube Transcript DL MCP Server

by jedarden
http-transport.ts8.71 kB
import { createServer, Server as HttpServer } from 'http'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { BaseTransport, TransportConfig } from './base-transport.js'; import express, { Request, Response } from 'express'; import { JSONRPCRequest, JSONRPCResponse } from '@modelcontextprotocol/sdk/types.js'; export interface HTTPTransportConfig extends TransportConfig { corsOrigin?: string; maxRequestSize?: number; timeout?: number; } export class HTTPTransport extends BaseTransport { private httpServer?: HttpServer; private app?: express.Application; private activeRequests: Map<string, { timestamp: number; request: JSONRPCRequest }> = new Map(); constructor(server: Server, config: HTTPTransportConfig) { super(server, { ...config, name: config.name || 'HTTP Transport' }); } public async start(): Promise<void> { if (this.isRunning) { this.logger.warn(`[${this.config.name}] Already running`); return; } try { const port = this.config.port || 3002; const host = this.config.host || 'localhost'; const corsOrigin = (this.config as HTTPTransportConfig).corsOrigin || '*'; const maxRequestSize = (this.config as HTTPTransportConfig).maxRequestSize || '10mb'; // Create Express app this.app = express(); // Configure middleware this.app.use(express.json({ limit: maxRequestSize })); // Configure CORS this.app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', corsOrigin); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.sendStatus(200); } else { next(); } }); // Request logging this.app.use((req, res, next) => { this.logger.debug(`[${this.config.name}] ${req.method} ${req.path}`); next(); }); // Health check endpoint this.app.get('/health', (req: Request, res: Response) => { res.json(this.getHealthStatus()); }); // JSON-RPC endpoint this.app.post('/rpc', async (req: Request, res: Response) => { const startTime = Date.now(); let requestId: string | undefined; try { const request = req.body as JSONRPCRequest; if (!this.isValidJSONRPCRequest(request)) { throw new Error('Invalid JSON-RPC request'); } requestId = String(request.id); // Track active request this.activeRequests.set(requestId, { timestamp: startTime, request }); // Process request through MCP server const response = await this.processRequest(request); // Send response res.json(response); // Log request completion const duration = Date.now() - startTime; this.logger.info(`[${this.config.name}] Request ${requestId} completed in ${duration}ms`); } catch (error) { const errorResponse: any = { jsonrpc: '2.0', id: requestId || 0, error: { code: -32603, message: 'Internal error', data: error instanceof Error ? error.message : 'Unknown error' } }; res.status(500).json(errorResponse); this.logger.error(`[${this.config.name}] Request error:`, error); } finally { if (requestId) { this.activeRequests.delete(requestId); } } }); // List available methods endpoint this.app.get('/methods', async (req: Request, res: Response) => { try { const tools = await this.getAvailableTools(); res.json({ jsonrpc: '2.0', result: { methods: tools.map(tool => ({ name: tool.name, description: tool.description, params: tool.inputSchema })) } }); } catch (error) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Failed to get methods', data: error instanceof Error ? error.message : 'Unknown error' } }); } }); // Create HTTP server this.httpServer = createServer(this.app); // Start server await new Promise<void>((resolve, reject) => { this.httpServer!.listen(port, host, () => { this.logger.info(`[${this.config.name}] Started on http://${host}:${port}`); this.isRunning = true; resolve(); }); this.httpServer!.on('error', (error) => { reject(error); }); }); // Start request cleanup timer this.startRequestCleanup(); } catch (error) { await this.handleError(error as Error); throw error; } } public async stop(): Promise<void> { if (!this.isRunning) { return; } this.logger.info(`[${this.config.name}] Stopping...`); // Wait for active requests to complete if (this.activeRequests.size > 0) { this.logger.info(`[${this.config.name}] Waiting for ${this.activeRequests.size} active requests...`); await this.waitForActiveRequests(); } // Close HTTP server if (this.httpServer) { await new Promise<void>((resolve) => { this.httpServer!.close(() => { this.logger.info(`[${this.config.name}] HTTP server closed`); resolve(); }); }); this.httpServer = undefined; } this.app = undefined; this.isRunning = false; } public getHealthStatus(): { status: string; details: any } { return { status: this.isRunning ? 'healthy' : 'stopped', details: { transport: 'HTTP', port: this.config.port || 3002, host: this.config.host || 'localhost', uptime: this.isRunning ? process.uptime() : 0, activeRequests: this.activeRequests.size, requestsServed: this.getRequestCount() } }; } private isValidJSONRPCRequest(request: any): boolean { return ( request && request.jsonrpc === '2.0' && typeof request.method === 'string' && (request.id === undefined || request.id !== null) ); } private async processRequest(request: JSONRPCRequest): Promise<JSONRPCResponse> { return new Promise((resolve, reject) => { // Create a custom transport that captures the response let responseReceived = false; const mockTransport = { send: async (message: any) => { if (!responseReceived) { responseReceived = true; resolve(message as JSONRPCResponse); } }, close: async () => {}, onclose: undefined, onerror: undefined, onmessage: undefined }; // Connect and send the request this.server.connect(mockTransport as any).then(() => { // Trigger the request processing if (mockTransport.onmessage) { (mockTransport.onmessage as any)(request); } }).catch(reject); // Timeout after 30 seconds setTimeout(() => { if (!responseReceived) { reject(new Error('Request timeout')); } }, 30000); }); } private async getAvailableTools(): Promise<any[]> { // This would need to be implemented to get tools from the MCP server // For now, return empty array return []; } private startRequestCleanup(): void { setInterval(() => { const timeout = (this.config as HTTPTransportConfig).timeout || 30000; const now = Date.now(); for (const [id, info] of this.activeRequests) { if (now - info.timestamp > timeout) { this.logger.warn(`[${this.config.name}] Request ${id} timed out`); this.activeRequests.delete(id); } } }, 5000); } private async waitForActiveRequests(): Promise<void> { const maxWait = 10000; // 10 seconds const startTime = Date.now(); while (this.activeRequests.size > 0 && Date.now() - startTime < maxWait) { await new Promise(resolve => setTimeout(resolve, 100)); } if (this.activeRequests.size > 0) { this.logger.warn(`[${this.config.name}] Force closing with ${this.activeRequests.size} active requests`); } } private getRequestCount(): number { // This would need to be tracked return 0; } }

Latest Blog Posts

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/jedarden/yt-transcript-dl-mcp'

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