Tutorial: Building Your First MCP Server in Python
Posted on June 10, 2026 • 5 min read • 920 wordsA step-by-step guide to building your own MCP server in Python: exposing tools and resources to an LLM, testing with the inspector, then connecting the server to a host such as Claude Desktop.

In the first article on Model Context Protocol servers, we laid the groundwork: its host / client / server architecture and its three primitives (tools, resources, prompts). Now for the practical part. In this tutorial, we build a small MCP server in Python from end to end, exposing one tool and one resource, then we connect it to a host to watch a model use it.
You need Python 3.10 or higher. The official SDK also ships a handy command-line utility to test and install a server.
# Create an isolated environment
python -m venv .venv
source .venv/bin/activate # on Windows: .venv\Scripts\activate
# Install the MCP SDK with its CLI tools
pip install "mcp[cli]"The mcp[cli] package adds the mcp command, which we’ll use to launch the inspector and install the server into a host.
The SDK provides FastMCP, a declarative API where you decorate plain Python functions. Let’s create a server.py file:
from mcp.server.fastmcp import FastMCP
# The name identifies the server to the host
mcp = FastMCP("Task Manager")
if __name__ == "__main__":
# In its most common setup, FastMCP uses stdio as the default transport. (standard input/output)
mcp.run()That’s all it takes for a valid server… but an empty one. It still does nothing useful: let’s give it some capabilities.
A tool is a function the model can call to act. You expose it with the @mcp.tool() decorator. Two details matter enormously:
title: str): they generate the schema the model reads to know how to call the tool.# In-memory storage for the example (lost on restart)
tasks: list[str] = []
@mcp.tool()
def add_task(title: str) -> str:
"""Add a task to the list and return a confirmation.
Args:
title: The label of the task to create.
"""
tasks.append(title)
return f"Task added: « {title} » (total: {len(tasks)})"The model will decide on its own when to invoke add_task, based on the name, the docstring, and the types. (If the host allows it and the user request lends itself to it.)
A resource provides read-only data to enrich the context. Unlike a tool, it causes no side effects: it merely returns information, identified by a URI.
@mcp.resource("tasks://list")
def list_tasks() -> str:
"""Return the list of saved tasks, one per line."""
if not tasks:
return "No tasks yet."
return "\n".join(f"- {t}" for t in tasks)A URI can also be parameterized. The braces capture a variable passed to the function:
@mcp.resource("tasks://{index}")
def task_detail(index: int) -> str:
"""Return the detail of a task from its number (starting at 1)."""
if 1 <= index <= len(tasks):
return tasks[index - 1]
return f"No task at number {index}."A simple rule to keep in mind: tool acts, a resource informs.
Before connecting anything to a model, let’s check the server in isolation. The MCP inspector is a web interface that connects to your server and lets you list and then run its tools and resources by hand.
mcp dev server.pyThe command starts the server and opens the inspector in the browser. You can then:
add_task) and resources (tasks://list);It’s the equivalent of a manual test: an excellent reflex before any integration.
Once the server is validated, let’s connect it to a real host. The fastest way:
# Depending on your SDK version
mcp install server.py
# or manual configurationThe SDK provides commands to ease integration with certain hosts. Behind the scenes, it adds an entry to the claude_desktop_config.json file. But depending on your version, you may have to add the entry to Claude Desktop’s configuration manually.
{
"mcpServers": {
"task-manager": {
"command": "python",
"args": ["/absolute/path/to/server.py"]
}
}
}After restarting the host, the server appears in the interface. Now ask the model, in natural language, to “add a task: prepare the presentation”: it will pick the add_task tool, ask for your approval, then run it. The loop is complete.
tasks list.In a few dozen lines, we built a working MCP server: a tool to act, a resource to inform, a test via the inspector, and a connection to a host. The most remarkable part is that all the business logic lives in the server — not in the model — which makes it testable, versionable, and reusable with any compatible client.
In the next article, we’ll leave the local machine to tackle integration into an ecosystem: remote transports, authentication, and security best practices for exposing an MCP server beyond your own machine.