Categories
AI

Write Your First MCP Server in 30 Minutes

Model Context Protocol lets any AI app talk to your own data and tools. This guide walks you from zero to a working MCP server in Python, connected to Claude Desktop, callable from a conversation, in under 30 minutes.

Model Context Protocol (MCP) is the open standard that lets AI apps like Claude Desktop talk to your own systems: databases, APIs, files, anything you can code against.

This guide gets you from zero to a working MCP server in Python, connected to Claude Desktop and callable from a conversation. No prior experience with protocols required.

What you’ll need

  • Python 3.10 or higher (check with python --version)
  • uv or pip for package installation
  • Claude Desktop installed (free tier works)
  • A terminal you’re comfortable running commands in

Step 1, Install the MCP library

The official MCP Python SDK from Anthropic ships with a high-level FastMCP interface that handles the protocol wiring for you. You write Python functions. The library does the rest.

pip install "mcp[cli]"

If you’re using uv, which is faster:

uv add "mcp[cli]"

Create a file called server.py in a new folder. Everything in this guide builds on that single file.

Step 2, Write a minimal working server

Paste this into server.py:

from mcp.server.fastmcp import FastMCP

# Give your server a name (Claude Desktop shows this in its settings)
mcp = FastMCP("MyFirstServer")

@mcp.tool()
def add_numbers(a: int, b: int) -> int:
    '''Add two integers and return the result.'''
    return a + b

@mcp.tool()
def greet(name: str) -> str:
    '''Return a greeting for the given name.'''
    return f"Hello, {name}. Your MCP server is working."

if __name__ == "__main__":
    mcp.run(transport="stdio")

That’s a complete, functional MCP server. It exposes two tools: add_numbers and greet. FastMCP reads your Python type hints and generates the JSON Schema Claude needs to call the tools correctly. The docstring becomes the tool description that Claude reads when deciding whether to use the tool. Keep docstrings specific: something like Add two integers and return the result works better than Does math.

Run it to confirm it starts without errors:

python server.py

It will appear to hang. That’s expected. The server is waiting for input on stdin. Press Ctrl+C to stop it.

Step 3, Connect it to Claude Desktop

Claude Desktop reads server configurations from a JSON file at startup. On macOS the path is ~/Library/Application Support/Claude/claude_desktop_config.json. On Windows, it’s %APPDATA%\Claude\claude_desktop_config.json.

Create or open that file and add the following. If the file already exists with other servers, add the my-first-server block inside the existing mcpServers object.

{
  "mcpServers": {
    "my-first-server": {
      "command": "python",
      "args": ["/absolute/path/to/your/server.py"]
    }
  }
}

Replace /absolute/path/to/your/server.py with the actual path to your file. On macOS, running pwd in your terminal shows the current directory. On Windows, cd does the same. The path must be absolute, not relative.

Restart Claude Desktop. In the chat interface, you should see a small tools icon near the bottom of the window. Click it and you’ll find add_numbers and greet listed under “MyFirstServer.”

Verifying it works

Open a new conversation in Claude Desktop and type:

Use the add_numbers tool to compute 17 + 25.

Claude will call your server, receive the result, and report 42. If your tools don’t appear, work through this checklist:

  • The file path in claude_desktop_config.json is absolute (not relative like ./server.py)
  • The Python binary in command is the correct one for your environment (run which python or where python to confirm)
  • You restarted Claude Desktop after editing the config
  • server.py runs cleanly when you test it manually in the terminal

Adding a real data source

Two arithmetic tools aren’t much to show off. Here’s how to add something that retrieves live data. Add this to server.py:

import urllib.request
import json

@mcp.tool()
def get_btc_price() -> str:
    '''Fetch the current Bitcoin price in USD from a public API.'''
    url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd"
    with urllib.request.urlopen(url) as resp:
        data = json.loads(resp.read())
        price = data["bitcoin"]["usd"]
        return f"Bitcoin is currently ${price:,} USD."

Restart Claude Desktop and ask Claude something like: what is the Bitcoin price right now? Claude will call your tool and return a live answer from CoinGecko‘s free API.

This is the core of what MCP enables. Your server can query a database, call an internal API, read a local file, run a calculation on private data. Anything Python can do, Claude can call, once the server exposes it as a tool.

Common pitfalls

  • Relative paths in config, claude_desktop_config.json requires absolute paths. Claude Desktop doesn’t have a working directory, so it can’t resolve ./server.py. Use the full path.
  • Missing type hints, FastMCP generates tool schemas from Python type hints. Omit them and the tool won’t appear in Claude’s list. Every parameter needs a type annotation.
  • Forgetting to restart, Claude Desktop caches server connections at startup. After any change to server.py or the config file, you must restart the app.
  • Wrong Python binary, If you’re using a virtual environment, the command field should point to the Python inside that environment, not the system Python. Run which python with the venv active to get the right path.
  • Vague docstrings, Claude reads your function docstrings to decide which tool to call. A vague description like Adds numbers is borderline. A specific one like Add two integers and return their sum, for arithmetic calculations is better. Specificity reduces wrong-tool calls.

Next steps

From here, you can expose data sources with @mcp.resource() using URI patterns, add reusable prompt templates with @mcp.prompt(), or deploy the server for remote access by switching transport="streamable-http" and updating the client config to use an HTTP URL instead of a command.

The MCP specification documents all three capability types in full. The reference servers repository on GitHub has production examples covering filesystems, databases, and third-party APIs. Most are under 200 lines. The protocol is young enough that simple API wrappers are still the most useful servers being built. If there’s an internal tool your team uses repeatedly, an MCP interface on top of it is usually a half-day project.

Sources