Simple Enough Blog logo
  • Home 
  • Projects 
  • Tags 

  •  Language
    • English
    • Français
  1.   Blogs
  1. Home
  2. Blogs
  3. Tutorial: Building Your First MCP Server in Python

Tutorial: Building Your First MCP Server in Python

Posted on June 10, 2026 • 5 min read • 920 words
LLM   Helene   MCP   Python   Tutorial  
LLM   Helene   MCP   Python   Tutorial  
Share via
Simple Enough Blog
Link copied to clipboard

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

On this page
I. Prerequisites and installation   II. The skeleton of a server with FastMCP   III. Exposing a tool   IV. Exposing a resource   V. Testing your server with the MCP inspector   VI. Connecting the server to a host (Claude Desktop)   VII. Best practices and common pitfalls   Conclusion   🔗 Useful links  
Tutorial: Building Your First MCP Server in Python
Photo by Helene Hemmerter

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.


I. Prerequisites and installation  

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.


II. The skeleton of a server with FastMCP  

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.


III. Exposing a tool  

A tool is a function the model can call to act. You expose it with the @mcp.tool() decorator. Two details matter enormously:

  • The type annotations (title: str): they generate the schema the model reads to know how to call the tool.
  • The docstring: it describes the tool to the model. Treat it like a user manual.
# 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.)


IV. Exposing a resource  

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.


V. Testing your server with the MCP inspector  

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

The command starts the server and opens the inspector in the browser. You can then:

  • see the list of tools (add_task) and resources (tasks://list);
  • call a tool with parameters and observe the response;
  • read a resource and check its content.

It’s the equivalent of a manual test: an excellent reflex before any integration.


VI. Connecting the server to a host (Claude Desktop)  

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 configuration

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


VII. Best practices and common pitfalls  

  • Describe everything precisely. The model only sees names, types, and docstrings. A vague description leads to wrong calls.
  • Validate inputs. Never trust parameters: check bounds, formats, and null values.
  • Think about security. A tool that writes to disk or calls a remote API must limit its scope. Expose only what’s strictly necessary.
  • Keep tools small and focused. One tool = one clear action; it’s easier to describe, test, and reason about.
  • Use in-memory storage only for learning. In production, a database or a file will replace our tasks list.

Conclusion  

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.


🔗 Useful links  

  • Official MCP Python SDK (GitHub)
  • Documentation: build an MCP server
  • MCP Inspector
  • MCP reference servers
 MCP Servers: Integration and Ecosystem
MCP Servers: Understanding the Model Context Protocol 
  • I. Prerequisites and installation  
  • II. The skeleton of a server with FastMCP  
  • III. Exposing a tool  
  • IV. Exposing a resource  
  • V. Testing your server with the MCP inspector  
  • VI. Connecting the server to a host (Claude Desktop)  
  • VII. Best practices and common pitfalls  
  • Conclusion  
  • 🔗 Useful links  
Follow us

We work with you!

   
Copyright © 2026 Simple Enough Blog All rights reserved. | Powered by Hinode.
Simple Enough Blog
Code copied to clipboard