Skip to main content
Glama

Weather MCP Server

index.ts12.6 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import express, { Request, Response } from 'express'; import { z } from 'zod'; import { configure } from '@vendia/serverless-express'; // Create an MCP server const server = new McpServer({ name: 'weather-mcp', version: '1.0.0' }); // Hardcoded cities database for testing const CITIES_DATABASE = [ { id: 1, cityName: 'New York', country: 'USA', latitude: 40.7128, longitude: -74.0060, population: 8336817 }, { id: 2, cityName: 'London', country: 'UK', latitude: 51.5074, longitude: -0.1278, population: 8982000 }, { id: 3, cityName: 'Paris', country: 'France', latitude: 48.8566, longitude: 2.3522, population: 2161000 }, { id: 4, cityName: 'Tokyo', country: 'Japan', latitude: 35.6762, longitude: 139.6503, population: 13960000 }, { id: 5, cityName: 'Sydney', country: 'Australia', latitude: -33.8688, longitude: 151.2093, population: 5312000 }, { id: 6, cityName: 'Berlin', country: 'Germany', latitude: 52.5200, longitude: 13.4050, population: 3645000 }, { id: 7, cityName: 'Toronto', country: 'Canada', latitude: 43.6532, longitude: -79.3832, population: 2930000 }, { id: 8, cityName: 'Mumbai', country: 'India', latitude: 19.0760, longitude: 72.8777, population: 20411000 }, { id: 9, cityName: 'São Paulo', country: 'Brazil', latitude: -23.5505, longitude: -46.6333, population: 12326000 }, { id: 10, cityName: 'Moscow', country: 'Russia', latitude: 55.7558, longitude: 37.6173, population: 11920000 }, { id: 11, cityName: 'Dubai', country: 'UAE', latitude: 25.2048, longitude: 55.2708, population: 3331000 }, { id: 12, cityName: 'Singapore', country: 'Singapore', latitude: 1.3521, longitude: 103.8198, population: 5454000 }, { id: 13, cityName: 'Barcelona', country: 'Spain', latitude: 41.3851, longitude: 2.1734, population: 1636000 }, { id: 14, cityName: 'Rome', country: 'Italy', latitude: 41.9028, longitude: 12.4964, population: 2873000 }, { id: 15, cityName: 'Amsterdam', country: 'Netherlands', latitude: 52.3676, longitude: 4.9041, population: 872000 }, { id: 16, cityName: 'Istanbul', country: 'Turkey', latitude: 41.0082, longitude: 28.9784, population: 15460000 }, { id: 17, cityName: 'Los Angeles', country: 'USA', latitude: 34.0522, longitude: -118.2437, population: 3980000 }, { id: 18, cityName: 'Mexico City', country: 'Mexico', latitude: 19.4326, longitude: -99.1332, population: 9209000 }, { id: 19, cityName: 'Seoul', country: 'South Korea', latitude: 37.5665, longitude: 126.9780, population: 9776000 }, { id: 20, cityName: 'Bangkok', country: 'Thailand', latitude: 13.7563, longitude: 100.5018, population: 10539000 } ]; // Register the city search tool that returns resources server.registerTool( 'searchCities', { title: 'Search Cities', description: 'Search for cities by name and return matching results as resources', inputSchema: { query: z.string().describe('City name or partial name to search for') }, outputSchema: { results: z.array(z.object({ id: z.number(), cityName: z.string(), country: z.string(), latitude: z.number(), longitude: z.number(), population: z.number() })), totalResults: z.number() } }, async ({ query }) => { // Filter cities that match the query (case-insensitive) const matchingCities = CITIES_DATABASE.filter(city => city.cityName.toLowerCase().includes(query.toLowerCase()) ); const output = { results: matchingCities, totalResults: matchingCities.length }; // Return as resources - each city is a separate resource const resourceContent = matchingCities.map(city => ({ type: 'resource' as const, resource: { uri: `city://${city.id}`, name: city.cityName, description: `${city.cityName}, ${city.country}`, mimeType: 'application/json', text: JSON.stringify(city, null, 2) } })); return { content: resourceContent, structuredContent: output }; } ); // Register the Fahrenheit to Celsius conversion tool (with outputSchema) server.registerTool( 'transformFahrenheitToCelsius', { title: 'Fahrenheit to Celsius Converter', description: 'Convert temperature from Fahrenheit to Celsius', inputSchema: { fahrenheit: z.number().describe('Temperature in Fahrenheit') }, outputSchema: { celsius: z.number(), fahrenheit: z.number(), formatted: z.string() } }, async ({ fahrenheit }) => { const celsius = ((fahrenheit - 32) * 5) / 9; const output = { celsius: parseFloat(celsius.toFixed(2)), fahrenheit, formatted: `${fahrenheit}°F is equal to ${celsius.toFixed(2)}°C` }; return { content: [{ type: 'text', text: JSON.stringify(output) }], structuredContent: output }; } ); // Register the Fahrenheit to Celsius conversion tool (without outputSchema) server.registerTool( 'transformFahrenheitToCelsiusNoSchema', { title: 'Fahrenheit to Celsius Converter (No Schema)', description: 'Convert temperature from Fahrenheit to Celsius without outputSchema', inputSchema: { fahrenheit: z.number().describe('Temperature in Fahrenheit') } }, async ({ fahrenheit }) => { const celsius = ((fahrenheit - 32) * 5) / 9; const output = { celsius: parseFloat(celsius.toFixed(2)), fahrenheit, formatted: `${fahrenheit}°F is equal to ${celsius.toFixed(2)}°C` }; return { content: [{ type: 'text', text: JSON.stringify(output) }] }; } ); // Register the city search tool without outputSchema server.registerTool( 'searchCitiesNoSchema', { title: 'Search Cities (No Schema)', description: 'Search for cities by name and return matching results as resources without outputSchema', inputSchema: { query: z.string().describe('City name or partial name to search for') } }, async ({ query }) => { // Filter cities that match the query (case-insensitive) const matchingCities = CITIES_DATABASE.filter(city => city.cityName.toLowerCase().includes(query.toLowerCase()) ); // Return as resources - each city is a separate resource const resourceContent = matchingCities.map(city => ({ type: 'resource' as const, resource: { uri: `city://${city.id}`, name: city.cityName, description: `${city.cityName}, ${city.country}`, mimeType: 'application/json', text: JSON.stringify(city, null, 2) } })); return { content: resourceContent }; } ); // Register the city search tool with text content and outputSchema server.registerTool( 'searchCitiesText', { title: 'Search Cities (Text)', description: 'Search for cities by name and return matching results as text with outputSchema', inputSchema: { query: z.string().describe('City name or partial name to search for') }, outputSchema: { results: z.array(z.object({ id: z.number(), cityName: z.string(), country: z.string(), latitude: z.number(), longitude: z.number(), population: z.number() })), totalResults: z.number() } }, async ({ query }) => { // Filter cities that match the query (case-insensitive) const matchingCities = CITIES_DATABASE.filter(city => city.cityName.toLowerCase().includes(query.toLowerCase()) ); const output = { results: matchingCities, totalResults: matchingCities.length }; // Return as text - single text content with all cities return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output }; } ); // Register the city search tool with text content and no outputSchema server.registerTool( 'searchCitiesNoSchemaText', { title: 'Search Cities (No Schema, Text)', description: 'Search for cities by name and return matching results as text without outputSchema', inputSchema: { query: z.string().describe('City name or partial name to search for') } }, async ({ query }) => { // Filter cities that match the query (case-insensitive) const matchingCities = CITIES_DATABASE.filter(city => city.cityName.toLowerCase().includes(query.toLowerCase()) ); const output = { results: matchingCities, totalResults: matchingCities.length }; // Return as text - single text content with all cities return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] }; } ); // Register the random image fetching tool server.registerTool( 'getRandomImage', { title: 'Get Random Image', description: 'Fetch a random image from picsum.photos with specified dimensions and return as base64-encoded image', inputSchema: { width: z.number().int().min(1).max(5000).describe('Image width in pixels'), height: z.number().int().min(1).max(5000).describe('Image height in pixels') } }, async ({ width, height }) => { try { // Fetch image from picsum.photos const url = `https://picsum.photos/${width}/${height}`; const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch image: ${response.statusText}`); } // Get the image as array buffer const arrayBuffer = await response.arrayBuffer(); // Convert to base64 const buffer = Buffer.from(arrayBuffer); const base64Data = buffer.toString('base64'); // Get content type from response headers (picsum returns jpeg) const mimeType = response.headers.get('content-type') || 'image/jpeg'; return { content: [ { type: 'image', data: base64Data, mimeType: mimeType } ] }; } catch (error) { // Return error as text content return { content: [ { type: 'text', text: `Error fetching image: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Set up Express and HTTP transport const app = express(); app.use(express.json()); app.post('/mcp', async (req: Request, res: Response) => { // Create a new transport for each request to prevent request ID collisions const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true }); res.on('close', () => { transport.close(); }); await server.connect(transport); await transport.handleRequest(req, res, req.body); }); // Health check endpoint app.get('/health', (req: Request, res: Response) => { res.json({ status: 'ok', service: 'weather-mcp' }); }); // Export handler for Lambda export const handler = configure({ app }); // Local development server if (process.env.NODE_ENV !== 'production' || !process.env.AWS_LAMBDA_FUNCTION_NAME) { const port = parseInt(process.env.PORT || '3000'); app.listen(port, () => { console.log(`Weather MCP Server running on http://localhost:${port}/mcp`); }).on('error', error => { console.error('Server error:', error); process.exit(1); }); }

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/mkowalskizoovu/mcp-test'

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