Skip to main content
Glama
danielbodnar

VyOS MCP Server

by danielbodnar
serve-docs.tsโ€ข5.38 kB
#!/usr/bin/env bun import { existsSync } from 'node:fs'; import { join } from 'node:path'; import { serve } from 'bun'; /** * @fileoverview Simple static file server for documentation using Bun. * Serves the TypeDoc generated documentation with proper MIME types. * * @author VyOS MCP Server * @version 1.0.0 * @since 2025-01-13 */ const DOCS_DIR = './docs'; const PORT = Number(process.env.PORT) || 8080; const HOST = process.env.HOST || 'localhost'; // MIME type mapping for common file extensions const MIME_TYPES: Record<string, string> = { '.html': 'text/html; charset=utf-8', '.css': 'text/css; charset=utf-8', '.js': 'application/javascript; charset=utf-8', '.json': 'application/json; charset=utf-8', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml; charset=utf-8', '.ico': 'image/x-icon', '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf', '.eot': 'application/vnd.ms-fontobject', '.txt': 'text/plain; charset=utf-8', '.md': 'text/markdown; charset=utf-8', }; /** * Get MIME type for a file extension */ function getMimeType(filePath: string): string { const ext = filePath.toLowerCase().substring(filePath.lastIndexOf('.')); return MIME_TYPES[ext] || 'text/plain; charset=utf-8'; } /** * Resolve file path and handle directory requests */ function resolvePath(url: string): string { let filePath = url === '/' ? '/index.html' : url; // Remove query parameters const queryIndex = filePath.indexOf('?'); if (queryIndex !== -1) { filePath = filePath.substring(0, queryIndex); } // Remove fragment const fragmentIndex = filePath.indexOf('#'); if (fragmentIndex !== -1) { filePath = filePath.substring(0, fragmentIndex); } // Normalize path and prevent directory traversal filePath = filePath.replace(/\\/g, '/').replace(/\/+/g, '/'); if (filePath.includes('..')) { return '/index.html'; } const fullPath = join(DOCS_DIR, filePath); // If it's a directory, try to serve index.html if (existsSync(fullPath) && Bun.file(fullPath).type === 'directory') { return join(filePath, 'index.html'); } return filePath; } /** * Create 404 error response */ function create404Response(): Response { const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>404 - Not Found</title> <style> body { font-family: Arial, sans-serif; margin: 40px; text-align: center; } .error { color: #e74c3c; } .back-link { margin-top: 20px; } a { color: #3498db; text-decoration: none; } a:hover { text-decoration: underline; } </style> </head> <body> <h1 class="error">404 - Page Not Found</h1> <p>The requested documentation page could not be found.</p> <div class="back-link"> <a href="/">โ† Back to Documentation Home</a> </div> </body> </html>`; return new Response(html, { status: 404, headers: { 'Content-Type': 'text/html; charset=utf-8', }, }); } /** * Main server function */ const server = serve({ port: PORT, hostname: HOST, async fetch(request: Request): Promise<Response> { const url = new URL(request.url); const filePath = resolvePath(url.pathname); const fullPath = join(DOCS_DIR, filePath); try { const file = Bun.file(fullPath); // Check if file exists if (!(await file.exists())) { console.log(`๐Ÿ“„ 404: ${url.pathname} (${fullPath})`); return create404Response(); } const mimeType = getMimeType(filePath); const content = await file.arrayBuffer(); console.log( `๐Ÿ“„ 200: ${url.pathname} (${(content.byteLength / 1024).toFixed(1)}KB)`, ); return new Response(content, { headers: { 'Content-Type': mimeType, 'Cache-Control': 'public, max-age=3600', // 1 hour cache 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }, }); } catch (error) { console.error(`โŒ Error serving ${url.pathname}:`, error); return new Response('Internal Server Error', { status: 500, headers: { 'Content-Type': 'text/plain; charset=utf-8', }, }); } }, error(error: Error): Response { console.error('โŒ Server error:', error); return new Response('Internal Server Error', { status: 500, headers: { 'Content-Type': 'text/plain; charset=utf-8', }, }); }, }); // Check if docs directory exists if (!existsSync(DOCS_DIR)) { console.error(`โŒ Documentation directory '${DOCS_DIR}' not found!`); console.log('๐Ÿ’ก Run "bun run docs" first to generate documentation.'); process.exit(1); } console.log(`๐Ÿ“š VyOS MCP Documentation Server`); console.log(`๐ŸŒ Server running at: http://${HOST}:${PORT}`); console.log(`๐Ÿ“ Serving files from: ${DOCS_DIR}`); console.log(`๐Ÿ”„ Press Ctrl+C to stop`); // Graceful shutdown process.on('SIGINT', () => { console.log('\n๐Ÿ‘‹ Shutting down documentation server...'); server.stop(); process.exit(0); }); process.on('SIGTERM', () => { console.log('\n๐Ÿ‘‹ Shutting down documentation server...'); server.stop(); process.exit(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/danielbodnar/vyos-mcp'

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