Build Your First MCP Server in 15 Minutes
A working tool server, from uv init to Claude Desktop.
What we're building
The fastest way to understand MCP is to ship a server. In the next fifteen minutes we will build a tiny tool server in Python, exercise it in the MCP Inspector, and connect it to Claude Desktop. No web server, no auth, no ceremony — just stdio and a function.
Setup with uv
The Python SDK ships as the mcp package, and FastMCP — the ergonomic, decorator-based API — comes with it. I use uv because it makes throwaway projects painless:
uv init weather-mcp
cd weather-mcp
uv add "mcp[cli]"Hello, FastMCP
Create server.py. A FastMCP server is an object you hang tools, resources, and prompts off of. Here is a complete, runnable server with one tool:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Weather")
@mcp.tool()
def get_forecast(city: str) -> str:
"""Return a short forecast for a city."""
return f"{city}: 22C and clear, light winds."
if __name__ == "__main__":
mcp.run()That is the entire server. The decorator registers get_forecast as an MCP tool; the type hints become the tool's input and output schema automatically, and the docstring becomes the description the model reads. You did not hand-write a single line of JSON Schema.
Exercise it in the Inspector
Before wiring anything into an AI client, test the server in isolation. The MCP Inspector is a one-command browser UI for poking at servers:
uv run mcp dev server.pyThat launches the Inspector at http://localhost:6274, starts your server over stdio, and lets you list tools and call get_forecast with a city. If a result comes back, your schema and handler are correct.
Connect it to Claude Desktop
Now make it real. Claude Desktop reads a JSON config — on macOS it lives at ~/Library/Application Support/Claude/claude_desktop_config.json. Add your server under mcpServers, using absolute paths:
{
"mcpServers": {
"weather": {
"command": "uv",
"args": ["--directory", "/Users/you/weather-mcp", "run", "server.py"]
}
}
}Fully quit and restart Claude Desktop — not just close the window — and your tool appears. Ask it for the forecast in Sydney and watch Claude call get_forecast.
The one rule that will save you an hour
Because stdio uses standard output for protocol messages, your server must never print to stdout. A stray print() corrupts the JSON-RPC stream and the client silently drops the connection. Log to stderr instead, or use the Context object's ctx.info(). This is the single most common reason a first server “doesn't work.”
Where to go next
You now have the loop every MCP project runs on: write a decorated function, test it in the Inspector, install it into a host. From here, add a resource for read-only data, return a Pydantic model for structured output, or flip to mcp.run(transport="streamable-http") to serve it over HTTP. The protocol is identical all the way up.