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) uvorpipfor 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.jsonis absolute (not relative like./server.py) - The Python binary in
commandis the correct one for your environment (runwhich pythonorwhere pythonto confirm) - You restarted Claude Desktop after editing the config
server.pyruns 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.jsonrequires 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.pyor the config file, you must restart the app. - Wrong Python binary, If you’re using a virtual environment, the
commandfield should point to the Python inside that environment, not the system Python. Runwhich pythonwith 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 numbersis borderline. A specific one likeAdd two integers and return their sum, for arithmetic calculationsis 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.