Skip to main content
Glama
numa08

NOAA Space Weather MCP Server

by numa08
server.ts7.12 kB
import http from 'node:http'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { CacheManager, DEFAULT_CACHE_CONFIG } from './cache/manager'; import { NoaaClient } from './noaa/client'; import type { CacheConfig, ServerConfig } from './noaa/types'; import { createToolHandlers, toolDefinitions } from './tools'; /** * Create and configure the MCP server * * Note: Using Server class (marked deprecated) due to Zod 4.x type compatibility issues with McpServer. * The Server class continues to work and is stable for production use. */ export function createServer(config?: Partial<CacheConfig>): { server: Server; client: NoaaClient; } { const cacheConfig = { ...DEFAULT_CACHE_CONFIG, ...config }; const cacheManager = new CacheManager(cacheConfig); const noaaClient = new NoaaClient(cacheManager); const toolHandlers = createToolHandlers(noaaClient); const server = new Server( { name: 'noaa-space-weather-mcp', version: '0.1.0', }, { capabilities: { tools: {}, }, }, ); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: toolDefinitions }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const handler = toolHandlers[name as keyof typeof toolHandlers]; if (!handler) { return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true, }; } try { // biome-ignore lint/suspicious/noExplicitAny: Dynamic tool dispatch return await (handler as (args: any) => Promise<any>)(args ?? {}); } catch (error) { return { content: [ { type: 'text', text: `Error executing ${name}: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }); return { server, client: noaaClient }; } /** * Run server in STDIO mode * * Note: When running in Docker, use Dockerfile.stdio (Node.js) instead of the * default Dockerfile (Bun) due to Bun's stdout buffering issue. * See: https://github.com/oven-sh/bun/issues/15893 */ export async function runStdioServer(config?: Partial<CacheConfig>): Promise<void> { const { server } = createServer(config); const transport = new StdioServerTransport(); await server.connect(transport); } /** * Run server in HTTP mode (Streamable HTTP transport) * * This implementation uses StreamableHTTPServerTransport in stateless mode, * which is ideal for serverless environments (AWS Lambda, Vercel, etc.) * where state cannot be preserved between requests. */ export async function runHttpServer(serverConfig: ServerConfig): Promise<void> { const port = serverConfig.port ?? 3000; const host = serverConfig.host ?? '0.0.0.0'; // Create shared cache and client for utility endpoints const cacheConfig = { ...DEFAULT_CACHE_CONFIG, ...serverConfig.cache }; const cacheManager = new CacheManager(cacheConfig); const sharedClient = new NoaaClient(cacheManager); const httpServer = http.createServer(async (req, res) => { const url = new URL(req.url ?? '/', `http://${req.headers.host}`); // CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, mcp-session-id'); // CORS preflight if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } // Health check endpoint if (url.pathname === '/health') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString() })); return; } // Cache stats endpoint if (url.pathname === '/stats') { const stats = sharedClient.getCacheStats(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(stats)); return; } // Root endpoint - info if (url.pathname === '/' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end( JSON.stringify({ name: 'noaa-space-weather-mcp', version: '0.1.0', description: 'MCP server for NOAA space weather data', transport: 'Streamable HTTP (stateless mode)', endpoints: { '/mcp': 'MCP Streamable HTTP endpoint (POST/GET/DELETE)', '/health': 'Health check', '/stats': 'Cache statistics', }, }), ); return; } // MCP endpoint using StreamableHTTPServerTransport if (url.pathname === '/mcp') { try { // Read request body for POST requests let body: unknown; if (req.method === 'POST') { const chunks: Buffer[] = []; for await (const chunk of req) { chunks.push(chunk as Buffer); } const rawBody = Buffer.concat(chunks).toString('utf-8'); if (rawBody) { body = JSON.parse(rawBody); } } // For stateless mode, create a new server and transport for each request // This is ideal for serverless environments const { server } = createServer(serverConfig.cache); // Create stateless transport (sessionIdGenerator: undefined) const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, // Stateless mode for serverless }); // Connect server to transport await server.connect(transport); // Handle the request using the transport await transport.handleRequest(req, res, body); } catch (error) { if (!res.headersSent) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end( JSON.stringify({ jsonrpc: '2.0', error: { code: -32603, message: error instanceof Error ? error.message : 'Internal error', }, id: null, }), ); } } return; } // Not found res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found'); }); httpServer.listen(port, host, () => { console.error(`NOAA Space Weather MCP Server running on http://${host}:${port}`); console.error('Endpoints:'); console.error(' POST/GET/DELETE /mcp - MCP Streamable HTTP endpoint'); console.error(' GET /health - Health check'); console.error(' GET /stats - Cache statistics'); console.error('Mode: Stateless (suitable for serverless deployment)'); }); }

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/numa08/noaa-space-weather-mcp'

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