What is an MCP Server, and How Do You Create One?
Building custom MCP server: Weather MCP Servers using Node.js

In the recent hype about the MCP server, last weekend I decided to explore it in depth and provide you with the real context about it to understand it without any confusion. I have learned about it and integrated it with my text editor (cursor and trae) and Claude AI desktop.
In this article, I will provide you with every important term about the MCP server and will show you how to create one. So let’s get started with it. See the Google trend data first. 👇

What is an MCP server?
An MCP (model context protocol) is a protocol to provide the context to the LLM models. It helps to communicate AI with external data sources (API, database, etc.). It helps to take input data from the user and create the structured payload (for the API call), and provide the human-readable data back to the user.
Where to use the MCP server?
MCP servers are playing an essential role in connecting the servers and apps together in your AI client. I am using it for various purposes; one of them is to post my tweets over X. I can simply provide the prompt to Claude Desktop to send a tweet about any provided topic, and it will post using the MCP server. There can be thousands of use cases available for which you can use the MCP server.
Some useful MCP servers are —
Time MCP—Provides time and timezone conversion ⏲
Filesystem MCP — Helps to interact with file on your system and perform actions 📁
How do MCP servers work?
MCP servers act as a middle layer between your API and service; it takes input from an AI model and creates the appropriate data to call a service to perform any action, like calling api, running a system command, analyzing data, etc.

Setting Up Your MCP Server
MCP does provide the SDKs for Python, TypeScript, Java, Kotlin, C#, and Swift to develop MCP servers. In this tutorial, I will show you how to create a simple weather forecast MCP using Node.js and run it through the Claude AI desktop client.
Prerequisite
Knowledge about Node.js and the Claude AI desktop app must be downloaded.
1. Install packages
Open your terminal and run the following commands —
mkdir weather-mcp # create a folder
cd weather-mcp # change directory to newly created folder
npm init -y # initialize the empty node project
# install required packages
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
mkdir src # create src folder
touch src/index.ts # create index.ts inside the src folder
2. Update package.json
Change type to module
{
"type": "module",
"bin": {
"weather": "./build/index.js"
},
"scripts": {
"build": "tsc && chmod 755 build/index.js"
},
"files": [
"build"
]
}
3. Create tsconfig.json file in root
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
4. Start building MCP server
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/1.0";
// Create server instance
const server = new McpServer({
name: "weather-mcp",
version: "1.0.0",
capabilities: {
resources: {},
tools: {},
},
});
// Helper function for making NWS API requests
async function makeNWSRequest<T>(url: string): Promise<T | null> {
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<AlertsResponse>(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<PointsResponse>(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<ForecastResponse>(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,
},
],
};
},
);
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);
});
5. Build
Run npm run build
to build the MCP server
Testing MCP server with Claude Desktop
To test our newly created MCP server, we have to add it to the Claude desktop app first. I am showing the steps for MAC Os —
- Download and install Claude AI Desktop app from https://claude.ai/download
- Open the setting

- Click on Developer > Edit Config

- Paste the following json to your file (make sure to change the path of the folder)
{
"mcpServers": {
"weather": {
"command": "node",
"args": [
"/path/to/your/weather-mcp/build/index.js"
]
}
}
}

- Quit and restart Claude Desktop, and then you will find the app integration inside your app.

- Write the prompt to execute the Weather MCP server

I have taken this example code from the official MCP server quickstart guide.
Thank you for taking the time to read this article! If you found it helpful, a couple of claps 👏 👏 👏 would be greatly appreciated — it motivates me to continue writing more. If you want to learn more about open-source and full-stack development, follow me on Twitter (X) and Medium.