Short answer: How to build an MCP server comes down to four steps: install an official SDK, define one tool, run the server over stdio and connect it to a client such as Claude for Desktop. The official quickstart builds a small weather server in about 100 lines, in Python with uv add "mcp[cli]" or in TypeScript with npm install @modelcontextprotocol/server zod. The one rule that breaks most first servers: a stdio server must never write to standard output.
An MCP server is a small program that gives an AI app a set of tools it can call. If you only want to use servers other people built, start with our MCP servers directory or the guide to using MCP servers in Claude. This guide is for the day you want to build your own. It follows the official Model Context Protocol quickstart and shows the commands in the order you run them.
Building an MCP server at a glance
| What you build | A weather server with two tools: get_alerts and get_forecast |
| Languages | Python, TypeScript and several others (see the SDK list below) |
| Python needs | Python 3.10 or higher and the Python MCP SDK 2.0.0 or higher |
| TypeScript needs | Node.js version 20 or higher |
| Transport | stdio, so the client launches your server as a local process |
| Client for testing | Claude for Desktop, plus the MCP Inspector |
| Biggest pitfall | Writing to stdout in a stdio server corrupts the messages |
| Cost | The SDKs are free; the weather data comes from the free US National Weather Service API |
What an MCP server does
MCP, the Model Context Protocol, is an open standard for connecting AI applications to external systems such as data sources, tools and workflows. A server sits on one side and exposes capabilities. A client, such as Claude for Desktop, Claude Code or Cursor, sits on the other and asks for them.
The docs name three kinds of capability a server can offer:
- Resources: file-like data a client can read, such as API responses or file contents.
- Tools: functions the model can call, with user approval.
- Prompts: pre-written templates that help people finish a task.
Your first server should offer tools only. The quickstart does the same. It is the part you will see working fastest, because the model decides when to call a tool and you can watch it happen.
To get a feel for what finished servers look like before you write one, open Filesystem MCP, which lets an assistant read and write files in folders you allow, or GitHub MCP Server, which works with repos, issues and pull requests. Both are official servers, and their tool lists are good models for how to name and scope your own tools.
Pick a language
The official SDK page lists Tier 1 SDKs for TypeScript, Python, C#, Go, Rust and Ruby, with Java at Tier 2 and Swift, PHP and Kotlin at Tier 3. All of them can create servers that expose tools, resources and prompts. The Python SDK and the TypeScript SDK have the most examples, so we use them here. Pick the one you already write.
Rule one: never print to stdout
Read this before you write any code. A stdio server talks to its client by sending JSON-RPC messages over standard output. Anything else you print there gets mixed into those messages and breaks the connection.
In Python, that means keeping print() out of the server entirely and using the logging module, which writes to stderr. In TypeScript, use console.error() instead of console.log(). HTTP-based servers can log to stdout, because their messages travel over HTTP instead.
import logging
logger = logging.getLogger(__name__)
print("Processing request") # breaks a stdio server
logger.info("Processing request") # safe: writes to stderr
Build the server in Python
You need Python 3.10 or higher and the Python MCP SDK 2.0.0 or higher.
Step 1: Set up the project
Install uv, the Python project tool the quickstart uses:
curl -LsSf https://astral.sh/uv/install.sh | sh
On Windows, the docs give a PowerShell one-liner instead. Restart your terminal afterward so the uv command is found. Then create the project:
uv init weather
cd weather
uv venv
source .venv/bin/activate
uv add "mcp[cli]"
touch weather.py
Step 2: Add the server and a helper
Add these to the top of weather.py:
from typing import Any
import httpx2
from mcp.server import MCPServer
mcp = MCPServer("weather")
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
The docs note that httpx2 is the HTTP client the SDK itself depends on, so installing mcp already brought it in. The MCPServer class reads Python type hints and docstrings to generate each tool’s definition for you. That is why the code below documents its arguments in the docstring: the model sees that text.
Next, a helper that calls the National Weather Service API and returns nothing if the request fails:
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
async with httpx2.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None
Step 3: Define a tool
A tool is a normal function with the @mcp.tool() decorator. This one returns active weather alerts for a US state:
@mcp.tool()
async def get_alerts(state: str) -> str:
"""Get weather alerts for a US state.
Args:
state: Two-letter US state code (e.g. CA, NY)
"""
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "Unable to fetch alerts or no alerts found."
if not data["features"]:
return "No active alerts for this state."
return "\n---\n".join(
feature["properties"].get("description", "No description available")
for feature in data["features"]
)
The official guide adds a second tool named get_forecast and a format_alert helper that lays each alert out as event, area, severity, description and instructions. Copy those from the quickstart once this version works.
Step 4: Run it
Add the entry point at the bottom of the file:
if __name__ == "__main__":
mcp.run(transport="stdio")
Then start it:
uv run weather.py
The server now waits for messages from an MCP host. Nothing prints, which is correct. To see it work, connect a client.
Build the server in TypeScript
You need the latest Node.js. The quickstart uses version 20 or higher. Check yours first:
node --version
npm --version
Then create the project and install the dependencies:
mkdir weather
cd weather
npm init -y
npm install @modelcontextprotocol/server zod
npm install -D @types/node typescript
mkdir src
touch src/index.ts
Update package.json to add "type": "module" and a build script of tsc && chmod 755 build/index.js, then add a tsconfig.json with the options from the quickstart. The server file starts with these imports:
import { McpServer } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import { z } from "zod";
You build the server inside a createServer() function that returns a new McpServer and registers each tool with server.registerTool. A tool has a name, a description, an input schema written with zod and an async handler that returns text content. You finish by serving it over stdio:
serveStdio(createServer, {
onerror: (error) => console.error("Weather MCP Server error:", error),
});
console.error("Weather MCP Server running on stdio");
serveStdio calls createServer() whenever it needs an instance, so the function must return a fresh server each time. The docs warn that wiring the server by hand with server.connect(new StdioServerTransport()) only serves older clients.
Finally, build it. The docs call this “a very important step in getting your server to connect”:
npm run build
Test it with the Inspector
Before you involve a client, check that the server lists its tools. For the TypeScript server, the quickstart gives this command:
npx @modelcontextprotocol/inspector --cli node build/index.js --protocol-era modern --method tools/list
The Inspector defaults to the legacy protocol era, so run the same command once more with --protocol-era legacy. Both runs should list get_alerts and get_forecast. If they do, the server works and any later failure is in the client setup.
Connect it to Claude for Desktop
Install Claude for Desktop and update it to the latest version. Then open its config file in a text editor, creating it if it does not exist:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json - Windows:
$env:AppData\Claude\claude_desktop_config.json
Add your server under the mcpServers key. For the Python server on macOS or Linux:
{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"weather.py"
]
}
}
}
For the TypeScript server, the command is node and the single argument is /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js.
Two details trip people up. The path must be absolute, and you can get it by running pwd. You may also need the full path to the uv executable in the command field, which which uv prints. Save the file and fully quit Claude for Desktop, then open it again. Closing the window is not enough.
Open the plus menu in the chat box and hover over Connectors. Your weather server should be listed. Ask “What are the active weather alerts in Texas?” and approve the tool call when prompted. The National Weather Service only covers US locations, so questions about other countries will fail.
What happens when you ask a question
The docs describe the loop in six steps:
- The client sends your question to Claude.
- Claude reads the available tools and decides which to use.
- The client runs the chosen tools through your MCP server.
- The results go back to Claude.
- Claude writes a plain-language answer.
- The answer appears in the chat.
Your code only runs in step three. The quality of steps two and five depends on the names and descriptions you gave your tools, so write them for a reader who has never seen your code.
Common errors and fixes
The server does not show up in Claude. Check the JSON syntax of claude_desktop_config.json, make sure the project path is absolute and fully quit and reopen the app.
Tool calls fail silently. Check Claude’s logs, confirm the server builds and runs on its own and restart the app. On macOS the logs live in ~/Library/Logs/Claude and on Linux in ~/.config/Claude/logs/. mcp.log covers connections, and mcp-server-SERVERNAME.log holds your server’s stderr output.
The connection drops right after start. Look for a stray print() or console.log(). It writes to stdout and corrupts the messages.
The weather tool returns “Failed to retrieve grid point data”. The coordinates are probably outside the US, the API is having trouble or you are being rate limited. Use US coordinates and add a small delay between requests.
“No active alerts” is not an error. It means the state has none right now.
Where to go next
Once the weather server works, replace it with something you need: a tool that searches your notes, queries a database or files tickets. Keep each tool small and give it a clear name. Read-only tools are the safest first choice, because a wrong call cannot change anything. If you are building a whole agent around your tools, see our guide on how to build an AI agent, and for the basics start with AI agents for beginners.
What this guide does not cover
The quickstart builds a local stdio server for one person. It does not cover remote servers that many people connect to over HTTP, sign-in with OAuth or hosting. Those are the next steps if you want to share your server, and the official docs cover them separately. The weather server is also a demo: it has no authentication, no caching and almost no error handling, so treat it as a template and not a finished product.
FAQ
Do I need to know a specific language to build an MCP server?
No. Official SDKs exist for TypeScript, Python, C#, Go, Rust, Ruby, Java, Swift, PHP and Kotlin. Pick the language you already use. The quickstart has tabs for several of them, and each SDK supports tools, resources and prompts.
Why does my MCP server break when I add a print statement?
A stdio server sends its messages over standard output, so extra output corrupts them. Use the logging module in Python or console.error() in TypeScript, both of which write to stderr and are safe.
Do I need Claude to test an MCP server?
No. The MCP Inspector can list and call your tools from the command line, which is the quickest check. Claude for Desktop is a good second test because it shows how a real client uses your tool descriptions.
What is the difference between a stdio and an HTTP server?
A stdio server runs as a local process that the client launches, which suits personal tools on your own machine. An HTTP server runs somewhere the client reaches over the network, which suits shared tools. This guide builds a stdio server.
Is it safe to give a server access to my files or accounts?
Only as safe as the tools you write. Start with read-only tools and limit them to the folders or data they need. Official servers such as Filesystem MCP work the same way: you list the folders it may touch.
Where can I find servers other people built?
Our MCP servers directory lists 100 servers in ten categories, each with its install command, sign-in method and price. Check it before you build, because the server you need may already exist.
Want to see what a finished server looks like before you write your own? Browse the MCP servers directory, and when your server is ready, compare it with the best AI coding agents that can call it.
