Building a Weather MCP Server: A Comprehensive Guide
Introduction to Model Context Protocol (MCP)
The Model Context Protocol (MCP) is an open standard that enables AI assistants to securely connect with external data sources, APIs, and services. Think of it as a standardized way for AI models to interact with the world beyond their training data. In this tutorial, we'll build a Weather MCP Server that allows AI assistants to fetch real-time weather information and alerts using the National Weather Service API.
Understanding MCP Architecture
Prerequisites
Before we begin, make sure you have:
- Node.js (v16 or higher) installed
- Basic knowledge of TypeScript/JavaScript
- Claude for Desktop installed (for testing)
Project Setup
Let's start by creating our project structure:
mkdir weather-mcp-server
cd weather-mcp-server
npm init -y
Install the required dependencies:
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
Configure your package.json
:
{
"type": "module",
"bin": {
"weather": "./build/index.js"
},
"scripts": {
"build": "tsc && chmod 755 build/index.js"
},
"files": ["build"]
}
Create a tsconfig.json
file:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Building the Weather MCP Server
Create the source directory and main file:
mkdir src
touch src/index.ts
Now let's implement our server:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const NWS_API_BASE = "https://api.weather.gov";
const USER_AGENT = "weather-app/1.0";
// Create server instance
const server = new McpServer({
name: "weather",
version: "1.0.0",
capabilities: {
resources: {},
tools: {},
},
});
// Helper function for making NWS API requests
async function makeNWSRequest(url: string): Promise {
const headers = {
"User-Agent": USER_AGENT,
Accept: "application/geo+json",
};
try {
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return (await response.json()) as T;
} catch (error) {
console.error("Error making NWS request:", error);
return null;
}
}
interface AlertFeature {
properties: {
event?: string;
areaDesc?: string;
severity?: string;
status?: string;
headline?: string;
};
}
// Format alert data
function formatAlert(feature: AlertFeature): string {
const props = feature.properties;
return [
`Event: ${props.event || "Unknown"}`,
`Area: ${props.areaDesc || "Unknown"}`,
`Severity: ${props.severity || "Unknown"}`,
`Status: ${props.status || "Unknown"}`,
`Headline: ${props.headline || "No headline"}`,
"---",
].join("\n");
}
interface ForecastPeriod {
name?: string;
temperature?: number;
temperatureUnit?: string;
windSpeed?: string;
windDirection?: string;
shortForecast?: string;
}
interface AlertsResponse {
features: AlertFeature[];
}
interface PointsResponse {
properties: {
forecast?: string;
};
}
interface ForecastResponse {
properties: {
periods: ForecastPeriod[];
};
}
// Register weather tools
server.tool(
"get_alerts",
"Get weather alerts for a state",
{
state: z.string().length(2).describe("Two-letter state code (e.g. CA, NY)"),
},
async ({ state }) => {
const stateCode = state.toUpperCase();
const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
const alertsData = await makeNWSRequest(alertsUrl);
if (!alertsData) {
return {
content: [
{
type: "text",
text: "Failed to retrieve alerts data",
},
],
};
}
const features = alertsData.features || [];
if (features.length === 0) {
return {
content: [
{
type: "text",
text: `No active alerts for ${stateCode}`,
},
],
};
}
const formattedAlerts = features.map(formatAlert);
const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
return {
content: [
{
type: "text",
text: alertsText,
},
],
};
}
);
server.tool(
"get_forecast",
"Get weather forecast for a location",
{
latitude: z.number().min(-90).max(90).describe("Latitude of the location"),
longitude: z
.number()
.min(-180)
.max(180)
.describe("Longitude of the location"),
},
async ({ latitude, longitude }) => {
// Get grid point data
const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
const pointsData = await makeNWSRequest(pointsUrl);
if (!pointsData) {
return {
content: [
{
type: "text",
text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
},
],
};
}
const forecastUrl = pointsData.properties?.forecast;
if (!forecastUrl) {
return {
content: [
{
type: "text",
text: "Failed to get forecast URL from grid point data",
},
],
};
}
// Get forecast data
const forecastData = await makeNWSRequest(forecastUrl);
if (!forecastData) {
return {
content: [
{
type: "text",
text: "Failed to retrieve forecast data",
},
],
};
}
const periods = forecastData.properties?.periods || [];
if (periods.length === 0) {
return {
content: [
{
type: "text",
text: "No forecast periods available",
},
],
};
}
// Format forecast periods
const formattedForecast = periods.map((period: ForecastPeriod) =>
[
`${period.name || "Unknown"}:`,
`Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
`Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
`${period.shortForecast || "No forecast available"}`,
"---",
].join("\n")
);
const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
return {
content: [
{
type: "text",
text: forecastText,
},
],
};
}
);
// Main function to run the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Weather MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
Building and Testing
Build the server:
npm run build
Configuring Claude for Desktop
To connect your weather server to Claude for Desktop, you need to create a configuration file:
-
Open your Claude for Desktop configuration file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
- Windows:
%APPDATA%\Claude\claude_desktop_config.json
- macOS:
-
Add the weather server configuration:
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/weather-mcp-server/build/index.js"]
}
}
}
Replace /ABSOLUTE/PATH/TO/weather-mcp-server/
with the actual path to your project.
- Save the file and restart Claude for Desktop.
Testing the Server
Once configured, you can test your server by asking Claude questions like:
- "What's the weather in Sacramento?"
- "What are the active weather alerts in Texas?"
- "Get the forecast for New York City"
How It Works
Advanced Features and Enhancements
Once you have the basic server working, consider these enhancements:
- Add Caching: Implement caching to reduce API calls
- Error Handling: Improve error messages and recovery
- Additional Data: Add more weather data points (humidity, precipitation, etc.)
- International Support: Integrate with other weather APIs for global coverage
- Location Search: Add geocoding to support city names instead of coordinates
Troubleshooting Common Issues
- Server not connecting: Check that the path in your config file is correct
- API errors: Verify your user agent string is properly formatted
- Build errors: Ensure TypeScript is properly configured
- Permission issues: Make sure the built JS file has execute permissions
Conclusion
Congratulations! You've built a functional Weather MCP Server that can provide real-time weather information and alerts to AI assistants. This server demonstrates the power of MCP in extending AI capabilities with real-world data.
The key takeaways from this tutorial are:
- MCP servers provide a standardized way for AI models to interact with external services
- Tools allow AI models to call functions with specific parameters
- Proper error handling and API management are crucial for production servers
- Claude for Desktop can easily integrate with custom MCP servers
You can extend this pattern to create MCP servers for any API or data source you want to make accessible to AI assistants.
Resources
- Model Context Protocol Official Documentation
- National Weather Service API Documentation
- Complete Example Code
Happy coding, and may your weather data always be accurate!