index.js•5.99 kB
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
import express from "express";
import { randomUUID } from "crypto";
// Weather API endpoint
const WEATHER_API = "https://api.open-meteo.com/v1/forecast";
// Define the input schema for our weather tool
const WeatherInputSchema = z.object({
latitude: z.number().describe("Latitude coordinate"),
longitude: z.number().describe("Longitude coordinate"),
});
// Create the MCP server instance
const server = new McpServer({
name: "weather-server",
version: "1.0.0",
capabilities: {
tools: {},
},
});
// Helper function to fetch weather data
async function fetchWeatherData(latitude, longitude) {
const params = new URLSearchParams({
latitude: latitude.toString(),
longitude: longitude.toString(),
current: "temperature_2m,wind_speed_10m",
hourly: "temperature_2m,relative_humidity_2m,wind_speed_10m",
});
const url = `${WEATHER_API}?${params.toString()}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Weather API returned status ${response.status}`);
}
return await response.json();
}
catch (error) {
throw new Error(`Failed to fetch weather data: ${error instanceof Error ? error.message : String(error)}`);
}
}
// Register the get_weather tool
server.registerTool("get_weather", {
title: "Get Weather Data",
description: "Fetches current weather and hourly forecast from Open-Meteo API for given coordinates",
inputSchema: {
latitude: z
.number()
.describe("Latitude coordinate (e.g., 52.52 for Berlin)"),
longitude: z
.number()
.describe("Longitude coordinate (e.g., 13.41 for Berlin)"),
},
}, async ({ latitude, longitude }) => {
try {
const weatherData = await fetchWeatherData(latitude, longitude);
// Format the response
const current = weatherData.current || {};
const hourly = weatherData.hourly || {};
let responseText = `Weather data for coordinates (${latitude}, ${longitude}):\n\n`;
responseText += `Current conditions:\n`;
responseText += `- Temperature: ${current.temperature_2m}°C\n`;
responseText += `- Wind Speed: ${current.wind_speed_10m} km/h\n\n`;
responseText += `Hourly forecast available for:\n`;
responseText += `- Temperature (${hourly.temperature_2m?.length || 0} hours)\n`;
responseText += `- Relative Humidity (${hourly.relative_humidity_2m?.length || 0} hours)\n`;
responseText += `- Wind Speed (${hourly.wind_speed_10m?.length || 0} hours)\n`;
// Include raw data for reference
responseText += `\nRaw API Response:\n${JSON.stringify(weatherData, null, 2)}`;
return {
content: [
{
type: "text",
text: responseText,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error fetching weather data: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});
// HTTP Server setup
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
// Store active transports by session ID
const transports = new Map();
// Health check endpoint
app.get("/health", (_req, res) => {
res.json({ status: "ok", server: "weather-mcp-server" });
});
// Main MCP endpoint
app.post("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"];
let transport;
if (sessionId && transports.has(sessionId)) {
// Use existing transport for this session
transport = transports.get(sessionId);
}
else {
// Create new transport for new session
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (newSessionId) => {
transports.set(newSessionId, transport);
console.log(`New session initialized: ${newSessionId}`);
},
});
transport.onclose = () => {
if (transport.sessionId) {
transports.delete(transport.sessionId);
console.log(`Session closed: ${transport.sessionId}`);
}
};
await server.connect(transport);
}
await transport.handleRequest(req, res, req.body);
});
// GET endpoint for SSE (Server-Sent Events)
app.get("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"];
if (!sessionId || !transports.has(sessionId)) {
res.status(400).json({ error: "Invalid or missing session ID" });
return;
}
const transport = transports.get(sessionId);
await transport.handleRequest(req, res);
});
// DELETE endpoint to close sessions
app.delete("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"];
if (!sessionId || !transports.has(sessionId)) {
res.status(400).json({ error: "Invalid or missing session ID" });
return;
}
const transport = transports.get(sessionId);
await transport.handleRequest(req, res);
});
// Start the HTTP server
async function main() {
app.listen(PORT, () => {
console.log(`\n🌤️ Weather MCP Server running on HTTP`);
console.log(`📡 Endpoint: http://localhost:${PORT}/mcp`);
console.log(`🏥 Health check: http://localhost:${PORT}/health`);
console.log(`\nPress Ctrl+C to stop the server\n`);
});
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});