FastMCP 2 Python Servers: Create, Build, and Run
- Scope: Practical guide for authoring, packaging, containerizing, and exposing Python MCP servers with FastMCP 2.x.
- References: See full implementations under
mcp-servers/python/*/server_fastmcp.py(20 example servers available), e.g.mcp-servers/python/chunker_server/src/chunker_server/server_fastmcp.pyandmcp-servers/python/url_to_markdown_server/src/url_to_markdown_server/server_fastmcp.py.
Project Layout
- Recommended structure for a new server
awesome_server:
awesome_server/
pyproject.toml
Makefile
Containerfile
README.md
src/
awesome_server/
__init__.py
server_fastmcp.py # FastMCP entry point
tools.py # optional: keep tool logic separate
tests/
test_server.py
Minimal Server (stdio + http)
- Implements a basic FastMCP server with one tool (
echo). Type hints define schemas.
# src/awesome_server/server_fastmcp.py
from fastmcp import FastMCP
mcp = FastMCP("awesome-server", version="0.1.0")
@mcp.tool
def echo(text: str) -> str:
"""Return the provided text."""
return text
def main() -> None:
"""Entry point for `python -m awesome_server.server_fastmcp`."""
mcp.run() # stdio by default
if __name__ == "__main__": # pragma: no cover
main()Enhanced Server with Native HTTP Support
- For better flexibility, add argument parsing to support both stdio and HTTP modes natively:
# src/awesome_server/server_fastmcp.py
from fastmcp import FastMCP
import argparse
mcp = FastMCP("awesome-server", version="0.1.0")
@mcp.tool
def echo(text: str) -> str:
"""Return the provided text."""
return text
def main() -> None:
"""Entry point with transport selection."""
parser = argparse.ArgumentParser(description="Awesome FastMCP Server")
parser.add_argument("--transport", choices=["stdio", "http"], default="stdio",
help="Transport mode (stdio or http)")
parser.add_argument("--host", default="0.0.0.0", help="HTTP host")
parser.add_argument("--port", type=int, default=8000, help="HTTP port")
args = parser.parse_args()
if args.transport == "http":
mcp.run(transport="http", host=args.host, port=args.port)
else:
mcp.run()
if __name__ == "__main__": # pragma: no cover
main()- Run over stdio:
python -m awesome_server.server_fastmcp - Run over HTTP:
python -m awesome_server.server_fastmcp --transport http --host 0.0.0.0 --port 8000 - Alternative with CLI:
fastmcp run src/awesome_server/server_fastmcp.py:mcp --transport http --host 0.0.0.0 --port 8000
pyproject.toml (template)
- Pin FastMCP for production deployments; adjust metadata and optional extras.
[project]
name = "awesome-server"
version = "0.1.0"
description = "Example FastMCP 2 server"
authors = [
{ name = "ContextForge", email = "noreply@example.com" }
]
license = { text = "MIT" }
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fastmcp==2.11.3",
"pydantic>=2.5.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-asyncio>=0.21.0",
"pytest-cov>=4.0.0",
"mypy>=1.5.0",
"ruff>=0.0.290",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/awesome_server"]
[project.scripts]
awesome-server = "awesome_server.server_fastmcp:main"
[tool.mypy]
python_version = "3.11"
strict = true
warn_return_any = true
warn_unused_configs = true
[tool.ruff]
line-length = 100
target-version = "py311"
select = ["E", "W", "F", "B", "I", "N", "UP"]
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
addopts = "--cov=awesome_server --cov-report=term-missing"Notes:
- Use exact FastMCP versions (
fastmcp==…) in production to avoid breaking changes. - See richer examples in
data_analysis_server/pyproject.tomlandmcp_eval_server/pyproject.tomlfor additional extras and entry points.
Makefile (template)
- Provides dev install, format/lint/test targets, multiple transport modes (stdio, native HTTP, SSE bridge).
# Makefile for Awesome FastMCP Server
.PHONY: help install dev-install format lint test dev serve-http serve-sse test-http mcp-info clean
PYTHON ?= python3
HTTP_PORT ?= 8000
HTTP_HOST ?= 0.0.0.0
help: ## Show help
@echo "Quick Start:"
@echo " make install Install FastMCP server"
@echo " make dev Run FastMCP server (stdio)"
@echo " make serve-http Run with native FastMCP HTTP"
@echo " make serve-sse Run with translate SSE bridge"
@echo ""
@awk 'BEGIN {FS=":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-18s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
install: ## Install in editable mode
$(PYTHON) -m pip install -e .
dev-install: ## Install with dev extras
$(PYTHON) -m pip install -e ".[dev]"
format: ## Format (ruff format + ruff --fix)
ruff format . && ruff check --fix .
lint: ## Lint (ruff, mypy)
ruff check . && mypy src/awesome_server
test: ## Run tests
pytest -v --cov=awesome_server --cov-report=term-missing
dev: ## Run FastMCP server (stdio)
$(PYTHON) -m awesome_server.server_fastmcp
serve-http: ## Run with native FastMCP HTTP
@echo "HTTP endpoint: http://$(HTTP_HOST):$(HTTP_PORT)/mcp/"
$(PYTHON) -m awesome_server.server_fastmcp --transport http --host $(HTTP_HOST) --port $(HTTP_PORT)
serve-sse: ## Run with mcpgateway.translate (SSE bridge)
@echo "SSE endpoint: http://$(HTTP_HOST):$(HTTP_PORT)/sse"
$(PYTHON) -m mcpgateway.translate --stdio "$(PYTHON) -m awesome_server.server_fastmcp" \
--host $(HTTP_HOST) --port $(HTTP_PORT) --expose-sse
test-http: ## Test native HTTP endpoint
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
http://$(HTTP_HOST):$(HTTP_PORT)/mcp/ | python3 -m json.tool | head -40 || true
mcp-info: ## Show MCP client configs
@echo "1. FastMCP Server (stdio - for Claude Desktop):"
@echo '{"command": "python", "args": ["-m", "awesome_server.server_fastmcp"]}'
@echo ""
@echo "2. Native HTTP: make serve-http"
@echo "3. SSE bridge: make serve-sse"
clean: ## Remove caches
rm -rf .pytest_cache .ruff_cache .mypy_cache __pycache__ */__pycache__ *.egg-infoNotes:
- Use
uv pip install -e .if your team standardizes on uv. - For richer Makefiles (container build, smoke tests, docs), see
mcp_eval_server/Makefile.
Containerfile (template)
- Minimal container using
python:3.11-slim; installs your project in a virtualenv with a non-root user.
# syntax=docker/dockerfile:1
FROM python:3.11-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PATH="/app/.venv/bin:$PATH"
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl && \
rm -rf /var/lib/apt/lists/*
COPY pyproject.toml README.md ./
COPY src/ ./src/
RUN python -m venv /app/.venv && \
/app/.venv/bin/pip install --upgrade pip setuptools wheel && \
/app/.venv/bin/pip install -e .
RUN useradd -u 10001 -m appuser && chown -R 10001:10001 /app
USER 10001
CMD ["python", "-m", "awesome_server.server_fastmcp"]Notes:
- Swap the container entrypoint to
fastmcp run /app/src/awesome_server/server_fastmcp.py:mcp --transport http --host 0.0.0.0 --port 8000(or similar) when you need remote HTTP access. - For hardened multi-stage builds (ubi-minimal runtime, non-root UID 10001, healthchecks), study
data_analysis_server/Containerfileandmcp_eval_server/Containerfile.
Run Locally
- Stdio mode (for local LLM clients or direct JSON-RPC piping):
make devfastmcp run src/awesome_server/server_fastmcp.py:mcp
- HTTP mode:
make serve-http- Call with curl:
curl -s -X POST http://localhost:8000/mcp/ -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Tips & Patterns
- Keep FastMCP objects (
FastMCP,@mcp.tool,@mcp.prompt,@mcp.resource) inserver_fastmcp.py; move heavy business logic intotools.pyor subpackages. - Log to stderr when running under stdio transports to avoid corrupting the protocol stream.
- Prefer Pydantic models for complex tool arguments/returns; FastMCP exposes them as structured schemas automatically.
- Add argparse for flexible transport selection (stdio/HTTP) in the same codebase.
- Combine FastMCP with the gateway by registering the HTTP endpoint (
/mcp) or by wrapping stdio servers withmcpgateway.translateif you need SSE bridging.
Best Practices (from production experience)
- Single Implementation: Use only FastMCP 2.x - avoid maintaining both MCP 1.0 and FastMCP versions
- Version Pinning: Always pin FastMCP to exact version (
fastmcp==2.11.3) to avoid breaking changes - Error Handling: Gracefully handle missing dependencies (e.g., Graphviz) with clear error messages
- Transport Flexibility: Support multiple transports in the same server:
- stdio for Claude Desktop and local clients
- Native HTTP for REST API access
- SSE bridge via translate for streaming clients
- Testing: Write tests that work directly with processor classes, not just via MCP protocol
- Project Structure: Keep it simple - one
server_fastmcp.pyfile is often sufficient for small/medium servers
FastMCP 2 Resources
- Core docs: Welcome to FastMCP 2.0, Installation, Quickstart, Changelog.
- Client guides: Client overview, Authentication (Bearer), Authentication (OAuth), User elicitation, Logging, Messages, Progress, Prompts, Resources, Tools, Transports, LLM sampling.
- Server guides: Server fundamentals, Context, Tools, Resources & templates, Prompts, Logging, Progress, Middleware, Authentication, Proxy, LLM sampling.
- Operations: Running your server, Self-hosted remote MCP, FastMCP Cloud, Project configuration.
- Integrations: FastAPI, Anthropic API, OpenAI API, Claude Desktop, Cursor.