Build a Custom MCP Server for Your API in 30 Minutes
Step-by-step TypeScript tutorial to build your first MCP server. Connect any API to Claude, ChatGPT, or other AI assistants using the Model Context Protocol.
I was knee-deep in debugging a deployment last week when I realized something frustrating: Claude could help me write code, but it couldn't check the weather API I was integrating. Every time I needed current data, I had to copy-paste responses back and forth. Then I discovered how to build a custom MCP server, and everything clicked.
TL;DR: Build an MCP Server in 4 Steps
- Install:
npm install @modelcontextprotocol/sdk zod - Create server: Initialize
McpServerwith name and version - Register tools: Define input schemas with Zod, write async handlers
- Connect: Use
StdioServerTransportand configure Claude Desktop
Result: Your API becomes a tool that Claude can call directly.
MCP—Model Context Protocol—is essentially the USB-C of AI integrations. Just like USB-C lets you connect any device to any port with one standard cable, MCP lets you connect any API to any AI assistant with one standard protocol. Build your MCP server once, and it works with Claude, ChatGPT (via plugins), and any MCP-compatible client.
Here's the thing: building an MCP server is surprisingly simple. In 30 minutes, you'll have a working server that exposes the National Weather Service API to Claude. Let's build it.
What is the Model Context Protocol (MCP)?
MCP is an open standard that Anthropic released in late 2024 for AI-tool communication. It defines how AI assistants can discover and call external tools, read resources, and interact with your systems.
Why MCP servers matter for developers:
- Extensibility: Add any capability to your AI assistant
- Modularity: Each MCP server handles one concern (weather, databases, etc.)
- Interoperability: Write once, works with multiple AI clients
Think of it this way: instead of teaching Claude about every API in the world, you build small MCP servers that know how to talk to specific APIs. Claude discovers these servers, learns what tools they offer, and calls them when needed.
MCP Server Project Setup (TypeScript)
First, let's scaffold our MCP server project:
mkdir weather-mcp-server && cd weather-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/nodeCreate a tsconfig.json for your MCP server:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}Update your package.json to add the build script and set the type:
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}Create your source file at src/index.ts. This is where the MCP server magic happens.
Building the MCP Server with TypeScript
Let's start with the imports and MCP server configuration:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import * as z from 'zod'
const server = new McpServer({
name: 'weather-server',
version: '1.0.0',
})The McpServer class is your main interface. It handles all the protocol details—you just register tools and let it do the heavy lifting.
Creating the API Helper Function
The NWS API is free and requires no authentication, making it perfect for this MCP server tutorial. Here's a helper function with proper error handling:
const NWS_BASE = 'https://api.weather.gov'
interface AlertFeature {
properties: {
event: string
headline: string
severity: string
urgency: string
areaDesc: string
}
}
interface ForecastPeriod {
name: string
temperature: number
temperatureUnit: string
windSpeed: string
shortForecast: string
}
async function fetchFromNWS<T>(endpoint: string): Promise<T> {
const response = await fetch(`${NWS_BASE}${endpoint}`, {
headers: {
'User-Agent': 'weather-mcp-server/1.0.0',
Accept: 'application/geo+json',
},
})
if (!response.ok) {
throw new Error(`NWS API error: ${response.status} ${response.statusText}`)
}
return response.json() as Promise<T>
}Notice the User-Agent header—NWS requires this for API requests. This is the kind of detail that catches developers off guard when building MCP servers.
Registering MCP Tools with Zod Validation
Now let's register our first MCP tool. Tools are functions that the AI can call. Each tool needs a name, description, input schema, and handler:
server.registerTool(
'get_alerts',
{
title: 'Get Weather Alerts',
description: 'Get active weather alerts for a US state',
inputSchema: {
state: z
.string()
.length(2)
.describe('Two-letter US state code (e.g., CA, TX, NY)'),
},
},
async ({ state }) => {
const data = await fetchFromNWS<{ features: AlertFeature[] }>(
`/alerts/active?area=${state.toUpperCase()}`
)
if (!data.features || data.features.length === 0) {
return {
content: [{ type: 'text', text: `No active alerts for ${state}` }],
}
}
const alerts = data.features.map((f) => ({
event: f.properties.event,
headline: f.properties.headline,
severity: f.properties.severity,
urgency: f.properties.urgency,
area: f.properties.areaDesc,
}))
return {
content: [{ type: 'text', text: JSON.stringify(alerts, null, 2) }],
}
}
)The inputSchema uses Zod for validation. When Claude calls this MCP tool, the SDK automatically validates the input before your handler runs. No manual parsing needed.
Adding a Second MCP Tool for Forecasts
Let's add a second tool for forecasts. This one requires two API calls—first to get the grid coordinates, then to fetch the forecast:
server.registerTool(
'get_forecast',
{
title: 'Get Weather Forecast',
description: 'Get 7-day weather forecast for a location',
inputSchema: {
latitude: z.number().min(-90).max(90).describe('Latitude coordinate'),
longitude: z
.number()
.min(-180)
.max(180)
.describe('Longitude coordinate'),
},
},
async ({ latitude, longitude }) => {
// First, get the grid point for this location
const pointData = await fetchFromNWS<{
properties: { forecast: string }
}>(`/points/${latitude},${longitude}`)
// Extract the forecast URL and fetch it
const forecastUrl = pointData.properties.forecast.replace(NWS_BASE, '')
const forecastData = await fetchFromNWS<{
properties: { periods: ForecastPeriod[] }
}>(forecastUrl)
const periods = forecastData.properties.periods.slice(0, 5).map((p) => ({
name: p.name,
temperature: `${p.temperature}${p.temperatureUnit}`,
wind: p.windSpeed,
forecast: p.shortForecast,
}))
return {
content: [{ type: 'text', text: JSON.stringify(periods, null, 2) }],
}
}
)Connecting Your MCP Server via Stdio Transport
The final piece is connecting your MCP server to the transport layer. MCP supports multiple transports, but stdio is the simplest for local development:
async function main() {
const transport = new StdioServerTransport()
await server.connect(transport)
console.error('Weather MCP server running on stdio')
}
main().catch(console.error)Important: Notice we use console.error for logging, not console.log. The stdio transport uses stdout for protocol messages—logging to stdout would corrupt the MCP communication.
Build your MCP server:
npm run buildHow to Connect Your MCP Server to Claude Desktop
Now let's wire this MCP server up to Claude. Open your Claude for Desktop config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Add your MCP server configuration:
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/absolute/path/to/weather-mcp-server/dist/index.js"]
}
}
}Replace /absolute/path/to/ with the actual path to your project. Save the file and restart Claude for Desktop.
To test your MCP server, open Claude and ask: "What weather alerts are active in California right now?"
If everything is configured correctly, Claude will discover your get_alerts tool and call it to fetch real data from the NWS API.
Key Takeaways for Building MCP Servers
Building MCP servers is straightforward once you understand the patterns:
-
Never log to stdout—use
console.errorinstead. Stdout is reserved for protocol messages, and logging there corrupts MCP communication. -
Zod handles input validation—define your schema once, and the SDK validates inputs automatically. No manual parsing or error handling for bad inputs.
-
MCP tools are just async functions—the MCP protocol handles discovery, schema exposure, and message routing. You focus on the business logic.
-
Start with stdio transport—it's the simplest for local MCP server development. You can add HTTP transport later for remote servers.
The complete code for this MCP server tutorial is available on GitHub. From here, you could extend this server with more tools: current conditions, radar images, or historical data. Or build entirely different MCP servers—databases, APIs, file systems—the pattern stays the same.
Frequently Asked Questions
What is an MCP server?
An MCP (Model Context Protocol) server is a lightweight program that exposes tools, resources, or prompts to AI assistants like Claude. It acts as a bridge between your APIs/systems and AI clients, allowing the AI to call your functions directly.
How do MCP servers communicate with Claude?
MCP servers communicate using the Model Context Protocol—an open standard for AI-tool communication. For local development, servers typically use stdio (standard input/output) transport. The server sends JSON-RPC messages over stdout, and receives requests over stdin.
Can I use MCP servers with AI assistants other than Claude?
Yes. MCP is an open protocol, so any AI client that implements MCP can connect to your server. This includes Claude Desktop, Claude Code, and potentially future integrations with other AI assistants like ChatGPT via plugins.
Why use Zod for MCP tool input validation?
Zod provides runtime type checking and automatic schema generation. When you define input schemas with Zod, the MCP SDK automatically validates incoming requests before your handler runs, eliminating manual parsing and error handling for invalid inputs.
And that concludes the end of this post! I hope you found this valuable and look out for more in the future!
Related Posts
If you enjoyed this tutorial on building MCP servers, you might also like:
- 15 Miraculous AI Agent Strategies for Code Generation in 2026 - Configure AI coding agents like Claude Code for better output with CLAUDE.md examples and MCP workflows
- 5 Voice Coding Patterns That Boosted My Productivity 3x with Claude Code - Another MCP integration example using VoiceMode for voice-driven development
- Designing API Methods in JavaScript - Fundamentals of good API design that apply when building MCP tools
Resources: