Skip to main content
In this lesson you’ll build a minimal MCP (Model Communication Protocol) server using the FastMCP Python library and run it locally with the STDIO transport. The guide shows how to:
  • Create a Python virtual environment
  • Expose simple tools: add, divide, and long_process
  • Return structured errors via a custom MCPError exception
  • Run and test the server locally with the MCP Inspector
  • Add logging to capture server activity
Prerequisite: Python 3.11+ (examples tested with Python 3.11). Table of contents
  • Setting up the project
  • Minimal starter main.py
  • Adding FastMCP and an add tool
  • Run the dev server and use the Inspector
  • Add error handling with divide
  • Simulate long-running work (long_process)
  • Transports and stdout warning
  • Add logging
  • Full consolidated example
  • Wrap-up and references

Setting up the project

Create a project and a virtual environment. This example uses the uv helper shown in this guide, but you can substitute with python -m venv .venv if preferred.
You should see output similar to:
Quick command reference

Minimal starter main.py

Start with a simple script to validate your environment:
Run it:
Expected output:

Adding FastMCP and a simple add tool

Install FastMCP (example uses uv add):
Replace main.py with a first MCP server that exposes a simple add tool:
Run the development server (this opens the Inspector and prints a session token):
The server prints a session token and provides a local MCP Inspector UI (by default at http://localhost:6274). Open the Inspector and authenticate by appending the query parameter shown in the console, for example: http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=<token>#resources (When copying, replace <token> with the value printed in your console.) Using the Inspector you can list tools, view tool docstrings (used as descriptions), and call tools. For example:
  • Calling add(3, 5) returns:

Adding structured error handling with divide

Introduce a small MCPError exception and add a divide tool that returns structured errors to callers:
Run mcp dev main.py again. The Inspector now shows both add and divide. Example responses:
  • Successful call divide(24, 2):
  • Error case divide(12, 0) — the Inspector and client will show the structured error message similar to:
Validation errors (e.g., passing floats where integers are required) are surfaced by pydantic-style validation messages that indicate the type mismatch and provide guidance.

Simulating long-running work

Some tools need to perform long-running tasks. Add long_process to simulate a multi-step job and show how STDIO behaves with longer processing times. Warning: writing arbitrary text to stdout while using the STDIO transport can corrupt the MCP message stream. Use logging or the library’s streaming/event APIs for progress updates.
Avoid printing arbitrary text to stdout when using the STDIO transport — it can interfere with the MCP protocol and break communication. Use logging or FastMCP’s streaming features for progress reporting.
Example long_process that uses logging (preferred):
Invoke long_process(10) from the Inspector to see how the server performs a short job. Increasing steps (e.g., long_process(100)) may trigger client or server timeouts depending on configured timeouts; timeouts are configurable on the server side.

Transports and the Inspector

STDIO is convenient for local development and tooling. FastMCP and the MCP Inspector also support other transports (for example, SSE or HTTP) when you need to expose your MCP server to other processes or to a network.

Adding logging

Enable Python logging to capture server activity in a file and to emit progress or input/output info from tools. Top-level logging setup:
Log inputs and outputs within your tools, for example:
After invoking tools via the Inspector, mcp_server.log will include entries similar to:

Full consolidated example

A compact main.py that combines the pieces:

Wrap-up

What you accomplished in this lesson:
  • Exposed functions as MCP tools using @mcp.tool()
  • Used docstrings for tool descriptions and argument schemas
  • Implemented structured errors with MCPError
  • Simulated long-running tasks and accounted for timeouts
  • Added logging to persist server events to a logfile
  • Ran and inspected the server locally via the MCP Inspector (STDIO transport)
You can now extend tools to access databases, call LLMs, perform async tasks, or expose your MCP server over other transports (HTTP/SSE) for remote clients. Links and references
  • FastMCP / MCP docs (check your installed package for local docs)
  • Python venv documentation
  • [MCP Inspector — local UI] — (open http://localhost:6274 when running mcp dev)
Example tool calls and expected outputs

Watch Video