Build a Weather MCP Server for Claude
Extend Claude Desktop with live weather tools using the Model Context Protocol.
Introduction
30 Second Summary
AI assistants like Claude are powerful, but they can only work with what they already know. What if you could give Claude the ability to check live weather forecasts just by asking?
In this project, you will build a weather MCP server in TypeScript that gives Claude Desktop the ability to fetch live forecasts and weather alerts from the National Weather Service API.
What You'll Build
You will open Claude Desktop, type "What's the weather in New York?", and watch Claude call your server to pull a live forecast from the National Weather Service.
By the end of this project, you'll have:
- Two weather tools (get_forecast and get_alerts) that Claude calls automatically to fetch live forecasts and active alerts for any US location.
- A city coordinates resource that Claude reads on its own to resolve place names like "Seattle" to latitude and longitude, so you never have to type coordinates manually.
- A travel briefing prompt you can select in Claude Desktop to get a full weather report for any trip, combining forecasts and alerts into packing recommendations and travel advice.
- Secret Mission: Add an hourly forecast tool and argument completions for state codes.
Are there any prerequisites?
You should have basic familiarity with JavaScript or TypeScript syntax. No prior AI development or API experience is required.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Your MCP Project
Before you can build a weather server, you need a properly configured TypeScript project. The MCP TypeScript SDK provides the building blocks for creating servers that any MCP-compatible client can connect to.
In this step, you'll create a project folder, install the SDK along with its dependencies, and write a minimal server file to confirm everything works.
In this step, get ready to:
- Verify Node.js is installed and ready.
- Create and configure a TypeScript project with the MCP SDK.
- Run the server to confirm it starts without errors.
Verify Node.js and create your project folder
- Check your Node.js installation by running this command in your terminal:
node --version
You should see a version number of v22.19.0 or higher.
Seeing a lower version or command not found?
The MCP tools you'll use in Step 2 require Node.js 22.19.0 or higher.
- If you see a version below 22, visit nodejs.org/en/download and install the latest LTS version.
- If the command is not found, visit the same page to install Node.js for the first time.
- After installing or upgrading, close and reopen your terminal, then run node --version again.
Help me troubleshoot my Node.js installation.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and initialize it by running these commands:
mkdir weather-mcp-server
cd weather-mcp-server
npm init -y
You should see a JSON output in your terminal showing the generated package.json file with default values.
What does npm init -y do?
This command creates a package.json file that tracks your project's name, version, and dependencies. The -y flag accepts all the defaults so you don't have to answer each prompt manually.
Install dependencies and create the server entry file
The default package.json needs a few changes before you install anything. You need to enable ES modules, pin exact dependency versions, and add a start script.
- Open VS Code by pressing the Windows key, typing VS Code, and pressing Enter.
- Click File in the top menu bar.
- Click Open Folder.
- Select the weather-mcp-server folder on your Desktop, then click Select Folder.
- In the Explorer sidebar on the left, click package.json to open it.
- Select all the existing content and replace it with this configuration:
{
"name": "weather-mcp-server",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "npx tsx src/index.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.30.0",
"zod": "3.25.67"
},
"devDependencies": {
"tsx": "4.19.4"
}
}
What does this configuration do?
- "type": "module" enables ES module syntax (import/export) instead of older CommonJS require() calls.
- @modelcontextprotocol/sdk is the official MCP TypeScript SDK that provides the server classes and transport layer.
- zod is a schema validation library the SDK uses to define tool input parameters.
- tsx lets you run TypeScript files directly without a separate compile step.
- Save package.json by pressing Ctrl+S.
- Install the dependencies listed in your package.json by running this in your terminal:
npm install
You should see output listing the installed packages, including @modelcontextprotocol/sdk, zod, and tsx.
Seeing permission errors or install failures?
- Make sure you are inside the weather-mcp-server folder. Run cd ~/Desktop/weather-mcp-server to get there.
- Check that your package.json is valid JSON. A missing comma or bracket will cause npm install to fail.
Help me troubleshoot my npm install.
Now you need a TypeScript file for the server code itself.
- Create the src folder by running:
mkdir src
- Confirm it exists by running:
ls
You should see node_modules, package.json, package-lock.json, and src listed in the output.
- In VS Code, click on the src folder in the Explorer sidebar.
- Click the New File icon at the top of the Explorer panel.
- Name the file index.ts and press Enter.
- Paste the following code into src/index.ts:
// Import the MCP server class and transports
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// NWS API configuration
const NWS_API_BASE = "https://api.weather.gov";
const USER_AGENT = "weather-mcp-server/1.0.0";
// Create the MCP server instance
const server = new McpServer({
name: "weather-server",
version: "1.0.0",
});
// Start the server using stdio transport
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Weather MCP Server running on stdio");
}
main().catch(console.error);
What does this code do?
- McpServer is the core class from the SDK that handles registering tools, resources, and prompts.
- StdioServerTransport connects over standard input/output. This is how Claude Desktop communicates with your server. It spawns it as a child process and talks over stdio.
- NWS_API_BASE and USER_AGENT are constants you'll use when calling the National Weather Service API in the next step.
- console.error is used instead of console.log because stdout is reserved for JSON-RPC messages between the client and server. Any console.log output would break the protocol.
- Save src/index.ts by pressing Ctrl+S.
✔️ Awesome, I've got everything!
Great. Double check that you saved both package.json and src/index.ts.
ⓧ I'd like to double check the full code
Here is the full package.json:
{
"name": "weather-mcp-server",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "npx tsx src/index.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.30.0",
"zod": "3.25.67"
},
"devDependencies": {
"tsx": "4.19.4"
}
}
Here is the full src/index.ts:
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-mcp-server/1.0.0";
const server = new McpServer({
name: "weather-server",
version: "1.0.0",
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Weather MCP Server running on stdio");
}
main().catch(console.error);
Run the server to confirm it starts
Everything is installed and the entry file is ready. Before you run the server, what do you think will happen?
- Start the server by running this command in your terminal:
npx tsx src/index.ts
You should see this message printed in your terminal:
Weather MCP Server running on stdio
The server is now running and waiting for a client to send it JSON-RPC messages over stdio. Since no client is connected, nothing else happens.
- Press Ctrl+C in your terminal to stop the server.
Not seeing the message or getting an error?
- If you see a "module not found" error, make sure your package.json has "type": "module" set. Without it, Node.js tries to use CommonJS and the import statements fail.
- If you see "Cannot find package", run npm install again to make sure your dependencies are installed.
- If the terminal hangs with no output at all, check that your console.error line in src/index.ts matches the code above exactly. Using console.log instead sends the message into the JSON-RPC channel where you won't see it.
Help me troubleshoot my MCP server startup.
Your MCP server starts and runs. Next up, you'll add your first weather tool and watch Claude call it through the MCP Inspector.
Build Your First Tool and Test It
Your MCP server starts up and prints its ready message, but it has zero capabilities. It cannot answer a single question about the weather.
Tools are the MCP primitive that let Claude take real actions on your behalf. The model reads a tool's name and description, decides when to call it, and sends back the result. In this step, you'll register two weather tools and test them with the MCP Inspector, a dedicated debugging UI for MCP servers.
In this step, get ready to:
- Add a get_forecast tool that fetches weather forecasts from the National Weather Service API.
- Add a get_alerts tool that retrieves active weather alerts for any US state.
- Test both tools in the MCP Inspector web UI.
Add the API helper function
Every tool you build will call the National Weather Service API. A shared helper function keeps that logic in one place so your tools stay focused on formatting and returning data.
- In src/index.ts, find this import line at the top of the file:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
- Update it to also import ResourceTemplate, so the line now looks like this:
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
- Next, find the line that says const USER_AGENT = "weather-mcp-server/1.0.0"; and add the following nwsFetch helper function directly below it:
async function nwsFetch(url: string): Promise<unknown> {
const response = await fetch(url, {
headers: {
"User-Agent": USER_AGENT,
Accept: "application/geo+json",
},
});
if (!response.ok) {
throw new Error(`NWS API error: ${response.status} ${response.statusText}`);
}
return response.json();
}
What does this code do?
The nwsFetch function wraps the built-in fetch with two things the NWS API requires: a User-Agent header identifying your app, and an Accept header requesting GeoJSON data.
If the API returns an error status, the function throws immediately so your tools never try to parse a failed response.
- Save src/index.ts.
- Confirm the server still compiles by running:
npx tsx src/index.ts
You should see Weather MCP Server running on stdio printed in the terminal. Press Ctrl+C to stop the server.
Seeing a syntax error?
Check that the nwsFetch function is placed between the USER_AGENT constant and the new McpServer constructor. It must sit outside any other function.
Also make sure the import line now reads { McpServer, ResourceTemplate } with a comma separating the two names.
Help me fix a syntax error in my MCP server after adding the nwsFetch helper function.
Register the forecast and alerts tools
With the helper ready, you can register your first tool. The get_forecast tool takes a latitude and longitude, hits two NWS endpoints in sequence, and returns a formatted forecast.
Why does the forecast need two API calls?
The NWS API uses a two-step lookup. First you call the /points endpoint with coordinates to discover which weather station covers that location. The response contains a forecast URL specific to that grid point.
Then you call that forecast URL to get the actual weather data. This two-step pattern means the NWS can route you to the most accurate local forecast rather than returning a generic one.
- In src/index.ts, find the line that creates the server:
const server = new McpServer({
name: "weather-server",
version: "1.0.0",
});
- Add the first part of the get_forecast tool directly below it. This registers the tool with its schema and fetches the raw forecast data:
server.registerTool(
"get_forecast",
{
title: "Get Weather Forecast",
description:
"Get the weather forecast for a location using latitude and longitude. Returns a 7-day forecast with 12-hour periods.",
inputSchema: {
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 }) => {
// Step 1: Get the grid point for these coordinates
const pointData = (await nwsFetch(
`${NWS_API_BASE}/points/${latitude},${longitude}`
)) as { properties: { forecast: string } };
// Step 2: Fetch the forecast from the grid-specific URL
const forecastUrl = pointData.properties.forecast;
const forecastData = (await nwsFetch(forecastUrl)) as {
properties: {
periods: Array<{
name: string;
temperature: number;
temperatureUnit: string;
windSpeed: string;
windDirection: string;
shortForecast: string;
detailedForecast: string;
}>;
};
};
What does this code do?
The registerTool call tells the MCP SDK about a new tool named get_forecast. The inputSchema uses Zod to validate that callers provide a valid latitude and longitude.
Inside the handler, the two-step NWS lookup first resolves the coordinates to a grid point, then fetches the forecast for that grid. The TypeScript as casts tell the compiler what shape the JSON response has.
- Now add the formatting and return logic directly below the code you just pasted, still inside the same handler function:
// Format the first 6 forecast periods into readable text
const periods = forecastData.properties.periods.slice(0, 6);
const formatted = periods
.map(
(p) =>
`${p.name}: ${p.temperature}°${p.temperatureUnit} | Wind: ${p.windSpeed} ${p.windDirection} | ${p.shortForecast}\n ${p.detailedForecast}`
)
.join("\n\n");
return {
content: [
{
type: "text" as const,
text: `Weather forecast for ${latitude}, ${longitude}:\n\n${formatted}`,
},
],
};
}
);
What does the formatting do?
The NWS returns forecast data in 12-hour periods ("Tonight", "Tuesday", "Tuesday Night", etc.). The code takes the first 6 periods and formats each one into a line showing the period name, temperature, wind, and a short description.
The content array with a text object is the standard MCP response format. Every tool must return data in this shape for the client to display it.
- Save src/index.ts.
- Confirm the tool registration compiles by running:
npx tsx src/index.ts
You should see Weather MCP Server running on stdio again with no errors. Press Ctrl+C to stop the server.
Seeing a type error or missing bracket?
The get_forecast tool spans two code blocks. Make sure the second block's code sits directly below the first block's closing }; line, still inside the async ({ latitude, longitude }) => { handler.
The final }); closes both the handler function and the registerTool call.
Help me fix a bracket or type error in my get_forecast tool registration.
Now add the second tool. The get_alerts tool fetches active weather alerts for any US state using a two-letter state code.
- In src/index.ts, add the first part of the get_alerts tool directly below the closing ); of get_forecast. This registers the tool and makes the API call:
server.registerTool(
"get_alerts",
{
title: "Get Weather Alerts",
description:
"Get active weather alerts for a US state. Provide a two-letter state code (e.g., CA, NY, TX).",
inputSchema: {
state: z
.string()
.length(2)
.describe("Two-letter US state code (e.g., CA, NY, TX)"),
},
},
async ({ state }) => {
// Fetch active alerts for this state
const data = (await nwsFetch(
`${NWS_API_BASE}/alerts/active?area=${state.toUpperCase()}`
)) as {
features: Array<{
properties: {
event: string;
headline: string;
description: string;
severity: string;
areaDesc: string;
};
}>;
};
What does this code do?
The get_alerts tool validates that the caller provides exactly a 2-character string using .length(2). It calls the NWS /alerts/active endpoint with the state code uppercased to handle mixed-case input.
- Add the response handling directly below, still inside the same handler:
// Handle the case where no alerts are active
if (!data.features || data.features.length === 0) {
return {
content: [
{
type: "text" as const,
text: `No active weather alerts for ${state.toUpperCase()}.`,
},
],
};
}
// Format up to 5 alerts with key details
const alerts = data.features.slice(0, 5);
const formatted = alerts
.map(
(a) =>
`Event: ${a.properties.event}\nSeverity: ${a.properties.severity}\nArea: ${a.properties.areaDesc}\nHeadline: ${a.properties.headline}\n${a.properties.description}`
)
.join("\n---\n");
return {
content: [
{
type: "text" as const,
text: `Active alerts for ${state.toUpperCase()} (showing up to 5):\n\n${formatted}`,
},
],
};
}
);
What does the response handling do?
The handler first checks if any alerts exist. If not, it returns a clean "no alerts" message instead of an empty result.
When alerts do exist, the code takes up to 5 and formats each one with its event type, severity, affected area, and headline. The --- separator makes it easy to distinguish individual alerts in the output.
- Save src/index.ts.
✔️ Awesome, I've got everything!
Make sure you've saved src/index.ts before moving on.
ⓧ I'd like to double check the full code
Your src/index.ts file is now too long to show in one block. Scroll up through your file and confirm these sections appear in order:
- The import line with McpServer and ResourceTemplate, plus the StdioServerTransport and zod imports.
- The NWS_API_BASE and USER_AGENT constants.
- The nwsFetch helper function.
- The new McpServer constructor.
- The get_forecast tool registration (with two API calls and formatting).
- The get_alerts tool registration (with the empty-check and alert formatting).
- The main function that creates the transport and connects.
Test both tools in the MCP Inspector
The MCP Inspector is a browser-based debugging tool that launches your server, connects to it over stdio, and lets you call tools interactively. It removes Claude from the equation so you can test your server in isolation.
- Launch the MCP Inspector with your server by running:
npx @modelcontextprotocol/inspector npx tsx src/index.ts
What does this command do?
The MCP Inspector spawns npx tsx src/index.ts as a child process, connects to it via stdio, and opens a web UI for you to interact with. It prints a URL containing a one-time session token in your terminal.
- Look for a URL in the terminal output that looks like http://127.0.0.1:6274 with a session token. Click the link that includes the token to open the Inspector in your browser.
- Click Connect in the Inspector to establish the connection to your server.
- Click the Tools tab in the top navigation.
You should see both get_forecast and get_alerts listed. Time to call your first tool.
Before you run it, think about what you expect to see. If you call get_forecast with New York City coordinates, what kind of data do you think the NWS will return?
- Select get_forecast from the tool list.
- Enter 40.7128 in the latitude field.
- Enter -74.006 in the longitude field.
- Click the call button to run the tool.
You should see a formatted weather forecast for the New York City area. The result includes named periods like "Tonight" and "Tuesday" with temperatures, wind data, and conditions.
- Now select get_alerts from the tool list.
- Enter CA in the state field.
- Click the call button.
You should see either a list of active weather alerts for California or a message saying no alerts are active. The exact content depends on current weather conditions.
Inspector not connecting or tools not showing?
- The MCP Inspector requires Node 22.19.0 or newer. Run node --version to check yours. If your version is older, update Node before retrying.
- Make sure you click the URL that includes the session token, not just the bare http://127.0.0.1:6274 address. The token is required for the connection.
- If the Tools tab is empty, make sure you clicked Connect first. The Inspector does not auto-connect.
Help me troubleshoot the MCP Inspector not connecting to my weather server.
Both tools return live data from the National Weather Service. Your server can now fetch forecasts and alerts for anywhere in the US. Next up, you'll add a resource that gives Claude a way to look up city coordinates on its own.
Add a Cities Resource
Your weather tools are live. You can fetch forecasts by coordinates and pull alerts by state code, all verified through the MCP Inspector.
But there's a catch. Both tools require exact latitude and longitude numbers. If someone asks "What's the weather in Seattle?", your server has no way to look up Seattle's coordinates on its own. In this step, you'll experience that gap firsthand, then fix it by adding a Resource.
In this step, get ready to:
- Spot the coordinate gap in your current tools.
- Add a cities resource with coordinate data for 10 major US cities.
- Test the resource in the MCP Inspector.
Spot the coordinate gap
Your get_forecast tool works perfectly when you know the coordinates. But think about how a real user talks to Claude.
- Switch back to the MCP Inspector in your browser.
- Click the Tools tab and select get_forecast.
- Look at the input fields. The tool requires a latitude number and a longitude number.
Now imagine Claude receives the message: "What's the weather in Seattle?" Claude sees your tool, but the tool only accepts numbers. Claude has no way to convert "Seattle" into coordinates through your server.
Every time someone mentions a city, they would need to manually supply the exact latitude and longitude. That's a usability dead end.
What is a Resource in MCP?
MCP defines three primitives. Tools are model-controlled actions the LLM decides when to call. Resources are application-controlled, read-only data that the client can expose to the model.
By adding a resource with city coordinates, Claude can look up "Seattle" in the resource, get the latitude and longitude, and then pass those numbers to your get_forecast tool. The resource bridges the gap between human-friendly city names and machine-friendly coordinates.
Add city coordinate data
You'll add this resource in two parts. First, a constant that maps 10 major US cities to their coordinates. Then, a resource registration that exposes that data through the MCP protocol.
- In src/index.ts, find the closing brace of the nwsFetch function. It looks like this:
return response.json();
}
- Add the following city coordinate data directly below the nwsFetch function, above the line that says const server = new McpServer:
// Coordinate lookup for major US cities
const CITIES: Record<string, { lat: number; lon: number }> = {
"New York": { lat: 40.7128, lon: -74.006 },
"Los Angeles": { lat: 34.0522, lon: -118.2437 },
Chicago: { lat: 41.8781, lon: -87.6298 },
Houston: { lat: 29.7604, lon: -95.3698 },
Phoenix: { lat: 33.4484, lon: -112.074 },
Seattle: { lat: 47.6062, lon: -122.3321 },
Denver: { lat: 39.7392, lon: -104.9903 },
Miami: { lat: 25.7617, lon: -80.1918 },
Atlanta: { lat: 33.749, lon: -84.388 },
Boston: { lat: 42.3601, lon: -71.0589 },
};
What does this code do?
CITIES is a TypeScript Record that maps city name strings to their latitude and longitude. These are the real coordinates for 10 major US cities.
This data is static. It doesn't call any API. The next chunk will register it as an MCP resource so Claude can read it.
- Save src/index.ts.
Register the cities resource
The data exists in your file, but your MCP server doesn't expose it yet. You need to register it as a resource so clients like Claude Desktop can discover and read it.
- In src/index.ts, scroll down past the get_alerts tool registration. Find the closing ); of that tool's server.registerTool call.
- Add the resource registration below that closing ); and above the async function main() block:
// --- Resources ---
// Expose city coordinates as read-only reference data
server.registerResource(
"us-major-cities",
"cities://us-major",
{
title: "Major US Cities",
description:
"Coordinates for major US cities. Use this to look up latitude and longitude for weather queries.",
mimeType: "application/json",
},
async (uri) => ({
contents: [
{
uri: uri.href,
text: JSON.stringify(CITIES, null, 2),
},
],
})
);
What does this code do?
- server.registerResource tells the MCP server to expose a new resource that clients can discover and read.
- The first argument "us-major-cities" is the resource's internal name. The second, "cities://us-major", is the URI that clients use to request it.
- The handler function returns the CITIES object as a pretty-printed JSON string. The mimeType: "application/json" tells the client what format to expect.
- Save src/index.ts.
Seeing a red squiggly line in your editor?
- Make sure the CITIES constant is defined above the const server line, not inside a function. The resource handler references CITIES at the module level.
- Check that the registerResource call is placed after the second registerTool call and before the main() function.
- Verify you have matching parentheses and braces. The handler uses => ({ (arrow returning an object literal wrapped in parentheses), which is easy to mistype.
Why is my resource registration showing an error?
Test the resource in the MCP Inspector
Before you run the test, think for a moment. What do you expect to see when you read this resource? What format will the data be in?
- Launch the MCP Inspector connected to your server by running this command:
npx @modelcontextprotocol/inspector npx tsx src/index.ts
- In the Inspector web UI, click the Resources tab in the left sidebar.
- Select cities://us-major from the resource list.
- Click Read Resource.
You should see a JSON object listing all 10 cities with their latitude and longitude coordinates. New York shows 40.7128 and -74.006, Seattle shows 47.6062 and -122.3321, and so on.
Why does this matter?
Now when Claude receives "What's the weather in Seattle?", it can read the cities://us-major resource to find Seattle's coordinates, then pass those numbers to get_forecast. The resource fills the gap between human language and machine-readable inputs.
Don't see the Resources tab or the resource?
- Make sure you saved src/index.ts before launching the Inspector. The Inspector starts a fresh server process each time.
- If the Resources tab is empty, stop the Inspector with Ctrl+C and re-run the npx @modelcontextprotocol/inspector command. A stale session from a previous run can miss new capabilities.
- Check that server.registerResource is called at the top level of the file, not inside the main() function.
My resource isn't showing up in the MCP Inspector
✔️ Awesome, I've got everything!
Great. Double check you've saved src/index.ts and your resource is returning city data in the Inspector.
ⓧ I'd like to double check the full code
Your src/index.ts is too long to show in one block. Scroll up and confirm each section is present in this order:
- Imports at the top: McpServer, ResourceTemplate, StdioServerTransport, and z from zod.
- NWS_API_BASE and USER_AGENT constants, then the nwsFetch helper function.
- The CITIES constant with 10 city entries (added in this step).
- The McpServer constructor, then get_forecast and get_alerts tool registrations.
- The server.registerResource call for cities://us-major (added in this step).
- The main() function at the bottom with StdioServerTransport and server.connect.
Your server now has tools for fetching weather data and a resource for looking up city coordinates. Next up, you'll connect this server to Claude Desktop so you can chat naturally and watch Claude use your tools and resource together in a real conversation.
Connect to Claude Desktop
Your weather server is proven. The MCP Inspector confirmed that both tools return real data and the resource serves city coordinates. Now it is time for the real payoff.
In this step, you will wire your server to Claude Desktop so it launches automatically and responds to plain English weather questions. No more typing tool names and raw coordinates. You will just ask Claude about the weather and watch it call your server behind the scenes.
In this step, get ready to:
- Download and install Claude Desktop.
- Configure Claude Desktop to auto-start your weather server.
- Test natural language weather queries in a real conversation.
Download and install Claude Desktop
Claude Desktop is the desktop app that acts as an MCP host. It spawns your server as a child process and communicates with it over stdio.
- Open your browser and go to claude.ai/download.
- Download the Windows installer and run it.
- Follow the installer prompts to complete the installation.
- Press the Windows key to open your search bar, type Claude, and press Enter to open it.
- Sign in with your Anthropic account if prompted.
Once you are signed in and see the main chat window, you are ready to open the config file.
- Click the hamburger menu icon in the top-left corner of Claude Desktop.
- Hover over File, then click Settings.
- Click Developer in the left sidebar.
- Click Edit Config.
This opens claude_desktop_config.json in your default text editor. The file may be empty or contain {}.
Where does this file live?
On Windows, the config file is stored at %APPDATA%\Claude\claude_desktop_config.json. The Edit Config button opens it directly so you do not need to navigate there yourself.
Add your weather server to the config
The config file tells Claude Desktop which MCP servers to launch. Each entry specifies the command to run and the absolute path to your server file.
- Switch back to your terminal (which should still be in the weather-mcp-server folder) and find your project's absolute path by running:
cd
You should see a path like C:\Users\YourName\Desktop\weather-mcp-server. Note this path down.
- Switch back to the claude_desktop_config.json file in your text editor.
- Replace everything in the file with the following config, updating the path to match your project location:
{
"mcpServers": {
"weather-server": {
"command": "npx",
"args": ["-y", "tsx", "[[SERVER_PATH="C:\\Users\\YourName\\Desktop\\weather-mcp-server\\src\\index.ts"]]"],
"env": {
"APPDATA": "[[APPDATA_PATH="C:\\Users\\YourName\\AppData\\Roaming\\"]]"}
}
}
}
What does this config do?
Each key inside mcpServers is one MCP server. Here is what each field means:
- "weather-server" is a friendly name that appears in Claude Desktop.
- "command": "npx" tells Claude Desktop to launch the server using Node.js's npx runner.
- "-y" auto-confirms any package download prompts so the server starts silently.
- "tsx" is the TypeScript runner that executes your src/index.ts without a build step.
- The env block passes the expanded APPDATA path so the server can resolve dependencies correctly on Windows.
Windows paths must use double backslashes (\\) because a single backslash is an escape character in JSON.
- Save the file.
- Fully quit Claude Desktop. Closing the window is not enough. Right-click the Claude icon in your system tray (bottom-right of the taskbar) and click Quit.
- Reopen Claude Desktop by pressing the Windows key, typing Claude, and pressing Enter.
Why fully quit?
Claude Desktop reads claude_desktop_config.json only at startup. Closing the window leaves the app running in the system tray. You must fully quit and relaunch so it picks up your new server entry.
- Verify your server connected by clicking the plus icon in the bottom-left corner of the chat input.
- Hover over Connectors.
- You should see weather-server listed. This confirms Claude Desktop found and started your MCP server.
Server not showing up?
- Double-check that the path in your config file matches the absolute path to src/index.ts on your machine. A wrong path is the most common cause.
- Make sure you used double backslashes (\\) in all Windows paths inside the JSON.
- Check the logs at %APPDATA%\Claude\logs for error details. Look for files starting with mcp.
- Try fully quitting Claude Desktop from the system tray and reopening it.
Still stuck? Why isn't my MCP server connecting to Claude Desktop?
Test a natural language weather conversation
Your server is connected. The real test is asking Claude a natural question and watching it call your tools automatically.
Before you try this, think about what you expect to happen. When you ask Claude about weather alerts, do you think it will call get_forecast, get_alerts, or both?
- Start a new conversation in Claude Desktop.
- Type this message and press Enter:
What are the current weather alerts for California?
- Claude may ask for permission to use the get_alerts tool. Click Allow to let it proceed.
Claude calls your get_alerts tool with state code CA and returns real weather alert data from the National Weather Service. You can see the tool call in Claude's response, showing exactly which tool it used and what arguments it passed.
What if there are no active alerts?
If California has no active weather alerts right now, Claude will tell you that. This is still a successful test. Your server returned a real response from the NWS API. Try asking about a different state with active weather, like TX or FL.
- Now test the full integration by asking Claude about weather in a specific city:
What's the weather forecast for Seattle?
Watch Claude's response. It uses the cities://us-major resource to look up Seattle's coordinates, then calls get_forecast with those coordinates. Your three MCP primitives (tool, resource, and soon a prompt) are working together in one natural conversation.
Claude not using your tools?
- Make sure you clicked Allow when Claude asked for tool permission. Denying it prevents the tool call.
- If Claude answers from general knowledge instead of calling a tool, try rephrasing: "Use your weather tools to check the forecast for Seattle."
- If you see an error in the response, check that your server runs correctly on its own by running npx tsx src/index.ts in the terminal.
Need help? Claude is not calling my weather MCP tools when I ask about weather.
Your MCP server is live inside Claude Desktop. You can ask about weather and Claude reaches for your tools automatically. Next up, you will add a prompt template that gives users a structured way to get a travel weather briefing.
Add a Prompt Template
Your weather server is live in Claude Desktop. Claude can fetch forecasts, check alerts, and look up city coordinates all on its own.
There is one more MCP primitive to add. Prompts are user-controlled templates that structure how you interact with your server. In this step, you will add a travel briefing prompt that composes your tools and resource into a single, reusable workflow.
In this step, get ready to:
- Add a travel-briefing prompt that composes your tools and resource into a structured weather query.
- Test the prompt in Claude Desktop by selecting it from the Connectors menu.
Add the travel-briefing prompt
MCP defines three primitives. Tools are model-controlled: Claude decides when to call them. Resources are application-controlled: the app loads them as context. Prompts are the third kind: user-controlled templates that the user explicitly selects.
A travel briefing prompt gives users a one-click way to ask Claude to check the forecast, look up alerts, and compile a packing-ready summary. Instead of typing a long, detailed question each time, the user picks the prompt and fills in two fields.
- In VS Code, open src/index.ts.
- Scroll down to the bottom of the file and find the main function:
async function main() {
- Add the following prompt registration above the main function:
// --- Prompts ---
server.registerPrompt(
"travel-briefing",
{
title: "Travel Weather Briefing",
description:
"Get a comprehensive weather briefing for travel planning. Provide the destination city and your travel dates.",
argsSchema: {
city: z.string().describe("Destination city name"),
dates: z.string().describe("Travel dates (e.g., 'September 15-20')"),
},
},
({ city, dates }) => ({
messages: [
{
role: "user" as const,
content: {
type: "text" as const,
text: `I'm planning to travel to ${city} on ${dates}. Please give me a comprehensive weather briefing:\n\n1. First, look up the coordinates for ${city} using the major US cities resource.\n2. Get the current weather forecast for that location.\n3. Check for any active weather alerts in that state.\n4. Summarize everything into a travel weather briefing with:\n - Current conditions and outlook\n - Any weather warnings or advisories\n - Packing recommendations based on the forecast\n - Any weather-related travel considerations`,
},
},
],
})
);
What does this code do?
server.registerPrompt registers a prompt named travel-briefing that Claude Desktop surfaces as a selectable template. The argsSchema uses Zod to define two required inputs: a city name and travel dates.
When a user selects the prompt, the handler function builds a structured message that instructs Claude to use the cities resource for coordinates, call get_forecast for the weather, check get_alerts for warnings, and compile everything into a travel-ready summary. One prompt orchestrates all three primitives together.
- Save src/index.ts.
Seeing a red underline in VS Code?
- Make sure the new code is placed above the async function main() line and below the closing ); of the registerResource call.
- Check that you have not accidentally deleted the closing parenthesis or semicolon of the registerResource block above.
- help me fix a syntax error in my MCP server prompt registration
Restart Claude Desktop and test the prompt
Claude Desktop only reads the server code when it first launches. You need to fully restart the app for it to pick up the new prompt.
- Fully quit Claude Desktop. Right-click the Claude icon in your system tray and select Quit. Closing the window alone does not stop the app.
- Reopen Claude Desktop.
- Start a new conversation.
- Click the + button at the bottom of the chat box.
- Hover over Connectors to open the menu. You should see your weather server listed.
- Select the Travel Weather Briefing prompt from the list.
Claude Desktop shows two input fields: one for the destination city and one for the travel dates.
- Enter Seattle in the city field.
- Enter September 15-20 in the dates field.
Before you submit, what do you expect Claude to do with this structured prompt? How many of your server's primitives will it use?
- Submit the prompt.
Claude reads the structured message, looks up Seattle's coordinates from the cities://us-major resource, calls get_forecast with those coordinates, checks get_alerts for Washington state, and compiles everything into a travel weather briefing with conditions, warnings, and packing recommendations.
Why use prompts instead of just typing a question?
You could type the same detailed question manually every time. A prompt template removes that friction. The user picks the template, fills in two fields, and gets a consistent, comprehensive result. For a server you share with others, prompts make your tools discoverable without the user needing to know the exact instructions.
Prompt not appearing in the Connectors menu?
- Make sure you fully quit Claude Desktop (system tray right-click, then Quit) and reopened it. Simply closing the window does not restart the server.
- Check Settings > Developer to confirm the weather server shows a green connected status.
- If the server shows an error, check the logs at %APPDATA%\Claude\logs for details. The file mcp-server-weather-server.log captures your server's output.
- help me debug why my MCP prompt is not showing up in Claude Desktop
✔️ Awesome, I've got everything!
Great. Double check you have saved src/index.ts and that the travel briefing worked in Claude Desktop.
ⓧ I'd like to double check the full code
Your src/index.ts file is now longer than 30 lines. Scroll through your file and confirm these sections appear in order:
- Imports (McpServer, ResourceTemplate, StdioServerTransport, z) at the top.
- NWS_API_BASE and USER_AGENT constants, the nwsFetch helper function, and the CITIES object.
- McpServer constructor: new McpServer({ name: "weather-server", version: "1.0.0" }).
- get_forecast tool registration.
- get_alerts tool registration.
- us-major-cities resource registration with URI cities://us-major.
- travel-briefing prompt registration with city and dates arguments.
- main function creating StdioServerTransport and calling server.connect(transport).
Your MCP server now has all three primitives: tools, a resource, and a prompt template. Next up, the Secret Mission takes your server even further with hourly forecasts and argument completions.
Secret mission
Add Hourly Forecasts and Argument Completions
Your weather server works, but it only shows 12-hour forecast periods. What if you need to know the weather at 3 PM specifically? And right now, users have to remember exact two-letter state codes for alerts. In this mission, you'll add an hourly forecast tool and wire up smart autocompletions so the server suggests state codes as you type.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally and uses the free NWS API, so there are no ongoing costs.
Resources you used:
- Claude Desktop configuration entry in claude_desktop_config.json.
- Local project folder (weather-mcp-server) with MCP server code and dependencies.
Keep everything running
No action needed. Choose this if you're still actively building or want to keep using the weather server in Claude Desktop.
- Your weather MCP server starts automatically whenever you launch Claude Desktop.
- The NWS API is free with no usage limits that would affect normal use, so leaving the server configured costs nothing.
- Your project folder stays on your Desktop with all code intact.
Pause - I'll come back to this later
Remove the server entry from Claude Desktop's config so it stops auto-starting. Your code stays intact on disk so you can reconnect later.
- In Claude Desktop, click the hamburger menu, then go to Settings > Developer > Edit Config.
- Delete the entire weather-server entry from the mcpServers object, then save the file.
- Fully quit Claude Desktop (not just close the window) and reopen it.
- To reconnect later, add the weather-server entry back to claude_desktop_config.json using the same config from Step 4.
Delete - I don't want to use this again
Remove the server from Claude Desktop and delete all project files from your machine.
- In Claude Desktop, click the hamburger menu, then go to Settings > Developer > Edit Config.
- Delete the entire weather-server entry from the mcpServers object, then save the file.
- Fully quit Claude Desktop and reopen it to confirm the server no longer appears.
- Delete the project folder by running this command on macOS/Linux:
rm -rf ~/Desktop/weather-mcp-server
On Windows, run this in PowerShell:
Remove-Item -Recurse -Force ~\Desktop\weather-mcp-server
- Confirm the folder is gone by running ls ~/Desktop (macOS/Linux) or ls ~\Desktop (Windows). You should no longer see weather-mcp-server listed.
Nice Work!
Nice Work!
You just built a custom MCP server from scratch and wired it into Claude Desktop. You can now open a chat, ask about the weather in any US city, and watch Claude call YOUR server to get the answer.
What you learned:
- Built a working MCP server using the TypeScript SDK with all three MCP primitives: Tools for fetching live weather data, a Resource for city coordinate lookups, and a Prompt template for structured travel briefings.
- Configured Claude Desktop to auto-start your server over stdio transport, experiencing the full integration loop from natural language question to live National Weather Service API data.
- Tested and debugged your server using the MCP Inspector, verifying each tool, resource, and prompt independently before connecting to a real client.
- Secret Mission: Extended the server with an hourly forecast tool and argument completions for state codes, giving the server a polished developer experience.
Ready to quiz yourself?