Skip to main content
Glama
RahulPatkiWork

YouTube Transcript MCP Server

index.ts7.99 kB
import { getTranscript } from './tools/transcript'; export interface Env { TRANSCRIPT_CACHE: KVNamespace; } // Simple MCP server implementation for Cloudflare Workers class SimpleMCPServer { private env: Env; constructor(env: Env) { this.env = env; } async handleRequest(request: any) { const { method, params, id } = request; try { switch (method) { case 'initialize': return { jsonrpc: '2.0', id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'youtube-transcript-remote', version: '1.0.0' } } }; case 'tools/list': return { jsonrpc: '2.0', id, result: { tools: [{ name: 'get_transcript', description: 'Extract transcript from YouTube video URL', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'YouTube video URL (any format)' }, language: { type: 'string', description: "Optional language code for the transcript (e.g., 'en', 'es'). Defaults to 'en'." } }, required: ['url'] } }] } }; case 'tools/call': const { name, arguments: args } = params; if (name === 'get_transcript') { try { const { url, language = 'en' } = args; const transcript = await getTranscript(url, this.env, language); return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: transcript }] } }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { jsonrpc: '2.0', id, error: { code: -1, message: errorMessage } }; } } else { return { jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } }; } default: return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${method}` } }; } } catch (error) { console.error('Error handling request:', error); return { jsonrpc: '2.0', id, error: { code: -32603, message: 'Internal error' } }; } } } // Export default object for ES Module format export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { const url = new URL(request.url); // Handle CORS preflight requests if (request.method === "OPTIONS") { return new Response(null, { status: 204, headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Cache-Control, Accept", "Access-Control-Max-Age": "86400" } }); } const mcpServer = new SimpleMCPServer(env); // Handle SSE endpoint for MCP if (url.pathname === "/sse") { // Check if this is a POST request with JSON-RPC data if (request.method === "POST") { try { const requestData = await request.json(); const response = await mcpServer.handleRequest(requestData); // Return as SSE format const sseData = `data: ${JSON.stringify(response)}\n\n`; return new Response(sseData, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Cache-Control, Accept", } }); } catch (error) { console.error('SSE POST error:', error); const errorResponse = `data: ${JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32603, message: "Internal error" } })}\n\n`; return new Response(errorResponse, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Access-Control-Allow-Origin": "*" } }); } } // Handle GET request for SSE connection const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); const encoder = new TextEncoder(); // Send initial connection message ctx.waitUntil((async () => { try { // Send server ready message await writer.write(encoder.encode(`data: ${JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized", params: {} })}\n\n`)); // Keep connection alive const keepAlive = setInterval(() => { writer.write(encoder.encode(`: keepalive\n\n`)).catch(() => { clearInterval(keepAlive); }); }, 30000); } catch (error) { console.error('SSE stream error:', error); } })()); return new Response(readable, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Cache-Control, Accept", } }); } // Handle JSON-RPC over HTTP POST if (url.pathname === "/mcp" && request.method === "POST") { try { const requestData = await request.json(); const response = await mcpServer.handleRequest(requestData); return new Response(JSON.stringify(response), { headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" } }); } catch (error) { console.error('MCP request error:', error); return new Response(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32603, message: "Internal error" } }), { status: 500, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" } }); } } // Handle root path with basic info if (url.pathname === "/") { return new Response(JSON.stringify({ name: "YouTube Transcript Remote MCP Server", version: "1.0.0", description: "Remote MCP server for extracting YouTube video transcripts", endpoints: { sse: "/sse", mcp: "/mcp" }, tools: ["get_transcript"], status: "ready" }), { headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" } }); } return new Response("Not Found", { status: 404 }); } };

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/RahulPatkiWork/youtube-transcript-mcp'

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