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.py

That 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.