Build MCP Servers in Python with FastMCP (stdio + HTTP)
Build a Model Context Protocol server in Python using FastMCP decorators — tools and resources from typed functions — then run it over stdio locally or Streamable HTTP remotely.
Prerequisites
- •Python 3.10+
- •Basic Python type hints
- •Parts 1–2 of this series recommended
What You'll Learn
- Create an MCP server with FastMCP decorators
- Expose tools and resources from typed Python functions
- Run over stdio for local hosts and Streamable HTTP for remote access
- Understand when to choose the Python vs TypeScript SDK
Model Context Protocol (MCP)
Python's MCP Superpower: Decorators
The official Python SDK ships FastMCP, an API that turns ordinary typed functions into MCP tools. Your type hints become the argument schema; your docstring becomes the description the model reads. If you know FastAPI, this will feel like home.
We'll build a sysinfo server — tools for disk usage and process lookup, plus a resource with machine facts — then run it both locally (stdio) and as a remote HTTP service.
1. Setup
mkdir sysinfo-mcp && cd sysinfo-mcp
python -m venv .venv && source .venv/bin/activate
pip install "mcp[cli]" psutil2. Tools from Typed Functions
Create server.py:
import shutil
import psutil
from mcp.server.fastmcp import FastMCPmcp = FastMCP("sysinfo")
@mcp.tool()
def disk_usage(path: str = "/") -> str:
"""Report total, used, and free disk space for a filesystem path.
Args:
path: Filesystem path to inspect (defaults to the root filesystem).
"""
usage = shutil.disk_usage(path)
gb = 1024 ** 3
return (
f"{path}: total {usage.total / gb:.1f} GB, "
f"used {usage.used / gb:.1f} GB, "
f"free {usage.free / gb:.1f} GB"
)
@mcp.tool()
def find_process(name: str) -> str:
"""Find running processes whose name contains the given text.
Args:
name: Case-insensitive substring of the process name.
"""
matches = [
f"{p.pid:>7} {p.info['name']} cpu={p.info['cpu_percent']:.1f}%"
for p in psutil.process_iter(["name", "cpu_percent"])
if name.lower() in (p.info["name"] or "").lower()
]
return "\n".join(matches[:20]) or f"No process matching '{name}'."
That's a complete tool definition. FastMCP reads the signature (path: str = "/") to build the JSON schema — including which arguments are optional — and the docstring becomes the tool description. Keep docstrings precise: they are your prompt engineering.
3. Add a Resource
Resources are read-only context identified by URIs. They're functions too:
import platform
@mcp.resource("sysinfo://machine")
def machine_info() -> str:
"""Static facts about this machine."""
return (
f"hostname={platform.node()}\n"
f"os={platform.system()} {platform.release()}\n"
f"python={platform.python_version()}\n"
f"cpus={psutil.cpu_count()}"
)
A host can attach sysinfo://machine to the conversation without any tool call — app-controlled context, exactly as Part 1 described.
4. Run It — stdio First
if __name__ == "__main__":
mcp.run() # stdio by defaultTest interactively with the dev inspector:
mcp dev server.pyRegister with Claude Code:
claude mcp add sysinfo -- python /absolute/path/to/sysinfo-mcp/server.pyOr with Claude Desktop via claude_desktop_config.json:
{
"mcpServers": {
"sysinfo": {
"command": "/absolute/path/to/sysinfo-mcp/.venv/bin/python",
"args": ["/absolute/path/to/sysinfo-mcp/server.py"]
}
}
}Tip: point command at the virtualenv's Python so imports resolve without activation.5. Going Remote: Streamable HTTP
stdio ties the server to one machine and one host process. To serve a team — or run on a box where the data actually lives — switch the transport:
if __name__ == "__main__":
mcp.run(transport="streamable-http")The server now listens over HTTP (default http://127.0.0.1:8000/mcp), speaking MCP's Streamable HTTP transport: clients POST JSON-RPC messages and responses can stream back. Remote servers change your security posture — add authentication in front (a reverse proxy with auth, or the SDK's auth hooks), run TLS, and scope credentials to least privilege before exposing anything beyond localhost.
6. Python or TypeScript?
Both SDKs are first-class; choose by ecosystem gravity:
npx distribution.Series Wrap-Up
You can now explain MCP's architecture (Part 1), ship a TypeScript stdio server with validated tools (Part 2), and ship Python servers over both transports (this part). The pattern from here is repetition: pick a system your team asks the assistant about, wrap its three most useful operations as well-described tools, and iterate on descriptions until the model uses them the way you'd hope. Small, sharp servers beat sprawling ones.

