Skip to main content
Glama

Weather MCP Service

by Philip-Walsh
validation.ts4.22 kB
import { Request, Response, NextFunction } from 'express'; import Joi from 'joi'; const weatherQuerySchema = Joi.object({ city: Joi.string().min(1).max(100).optional(), days: Joi.number().integer().min(1).max(10).optional() }); const mcpRequestSchema = Joi.object({ jsonrpc: Joi.string().valid('2.0').required(), id: Joi.alternatives().try(Joi.string(), Joi.number()).optional(), method: Joi.string().required(), params: Joi.object().optional() }); export const sanitizeInput = (req: Request, res: Response, next: NextFunction) => { // Sanitize query parameters if (req.query) { Object.keys(req.query).forEach(key => { if (typeof req.query[key] === 'string') { req.query[key] = req.query[key]?.toString().trim(); } }); } // Sanitize body for MCP requests if (req.body && typeof req.body === 'object') { Object.keys(req.body).forEach(key => { if (typeof req.body[key] === 'string') { req.body[key] = req.body[key].trim(); } }); } next(); }; export const validateWeatherQuery = (req: Request, res: Response, next: NextFunction) => { const { error, value } = weatherQuerySchema.validate(req.query); if (error) { return res.status(400).json({ error: 'Invalid query parameters', details: error.details.map(d => d.message) }); } req.query = value; next(); }; export const validateMCPRequest = (req: Request, res: Response, next: NextFunction) => { const { error, value } = mcpRequestSchema.validate(req.body); if (error) { return res.status(400).json({ jsonrpc: '2.0', id: req.body.id || null, error: { code: -32700, message: 'Parse error', data: error.details.map(d => d.message) } }); } req.body = value; next(); }; export const createRateLimit = (windowMs: number, max: number) => { const requests = new Map<string, { count: number; resetTime: number }>(); return (req: Request, res: Response, next: NextFunction) => { const ip = req.ip || req.connection.remoteAddress || 'unknown'; const now = Date.now(); const windowStart = now - windowMs; // Clean up old entries for (const [key, value] of requests.entries()) { if (value.resetTime < windowStart) { requests.delete(key); } } const userRequests = requests.get(ip); if (!userRequests) { requests.set(ip, { count: 1, resetTime: now }); return next(); } if (userRequests.resetTime < windowStart) { requests.set(ip, { count: 1, resetTime: now }); return next(); } if (userRequests.count >= max) { return res.status(429).json({ error: 'Too many requests', message: `Rate limit exceeded. Max ${max} requests per ${windowMs / 1000} seconds`, retryAfter: Math.ceil((userRequests.resetTime + windowMs - now) / 1000) }); } userRequests.count++; next(); }; }; export const validateAPIKey = (req: Request, res: Response, next: NextFunction) => { const apiKey = req.headers['x-api-key'] as string; // For now, we don't require API keys, but this is intended for future use if (apiKey && apiKey.length < 10) { return res.status(401).json({ error: 'Invalid API key', message: 'API key must be at least 10 characters long' }); } next(); }; export const securityHeaders = (req: Request, res: Response, next: NextFunction) => { // Remove server header res.removeHeader('X-Powered-By'); // Add security headers res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-XSS-Protection', '1; mode=block'); res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()'); next(); };

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/Philip-Walsh/weathernode'

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