Python FastAPI MCP Server Starter
Overview
This template gives you a production-ready foundation for building Model Context Protocol (MCP) servers in Python using FastAPI as the HTTP transport layer. It is designed for developers who are new to MCP and want a clean, well-structured starting point that follows real-world best practices rather than toy examples.
The server implements three realistic tools โ a text analyser, a JSON transformer, and a system information utility โ demonstrating how to handle input validation, structured error responses, and async execution in an MCP context. Every component is wired together with API key authentication and structured JSON logging so you can deploy with confidence from day one.
This template is ideal for Python developers building internal tooling, AI-assisted workflows, or API-backed MCP servers that need to run reliably in containerised environments. Whether you are connecting to Claude Desktop for local experimentation or deploying to Railway or Fly.io for team use, this template handles the boilerplate so you can focus on your domain logic.
What You'll Learn
- How the Model Context Protocol (MCP) works over HTTP transport and why it differs from stdio
- How to register and implement MCP tools using the
mcpPython SDK with FastAPI - How to protect an MCP server with API key authentication using FastAPI middleware
- How to configure structlog for JSON-formatted logs with per-request correlation IDs
- How to load and validate environment-based configuration using Pydantic Settings
- How to write a multi-stage Dockerfile that produces a minimal, secure production image
- How to use docker-compose for local development with live reload and environment injection
- How to validate tool inputs with Pydantic models and return structured error responses
- How to implement async tool handlers that are safe for concurrent MCP clients
- How to expose a health check endpoint alongside MCP routes for deployment monitoring
Architecture
Claude Desktop / MCP Client
โ
โ HTTP POST /mcp (Authorization: Bearer <api-key>)
โผ
FastAPI Application
โ
โโโ API Key Auth Middleware (auth.py)
โ โ reject โ 401 Unauthorized
โ โผ
โโโ Request Logger Middleware (logger.py)
โ โ attaches correlation_id to every log line
โ โผ
โโโ MCP Router (server.py)
โ
โโโ tool: analyze_text โ NLP utilities
โโโ tool: transform_json โ Data reshaping
โโโ tool: system_info โ Runtime metadata
Project Structure
python-fastapi-mcp-starter/
โโโ server.py # Main FastAPI + MCP application
โโโ auth.py # API key authentication middleware
โโโ config.py # Pydantic Settings configuration
โโโ logger.py # structlog setup and middleware
โโโ requirements.txt # Pinned Python dependencies
โโโ Dockerfile # Multi-stage production image
โโโ docker-compose.yml # Local development stack
โโโ .env.example # Environment variable reference
โโโ .gitignore # Python / Docker ignores
Prerequisites
- Python 3.11 or higher
- Docker and Docker Compose (for containerised development)
- An MCP client such as Claude Desktop, Cursor, or Windsurf
- Basic familiarity with Python async/await and REST APIs
Quick Start
# 1. Clone or download this template
git clone https://github.com/your-org/python-fastapi-mcp-starter.git
cd python-fastapi-mcp-starter
# 2. Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 3. Install dependencies
pip install -r requirements.txt
# 4. Copy and edit environment variables
cp .env.example .env
# Edit .env โ set MCP_API_KEY to a strong secret
# 5. Run the server locally
python server.py
# Server starts on http://localhost:8000
# 6. Verify the health endpoint
curl http://localhost:8000/health
# 7. Test an MCP tool call (replace YOUR_KEY)
curl -X POST http://localhost:8000/mcp \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"system_info","arguments":{}}}'
Running with Docker Compose:
# Start the full stack
docker compose up --build
# Run in background
docker compose up -d --build
# View logs
docker compose logs -f mcp-server
# Stop
docker compose down
Features
- HTTP Transport โ Runs as a standard HTTP server; easy to reverse-proxy, load-balance, and monitor
- API Key Authentication โ Every request validated against a configurable secret key via Bearer token
- Three Realistic Tools โ
analyze_text,transform_json, andsystem_infowith full input validation - Structured JSON Logging โ structlog with ISO timestamps, log levels, and per-request correlation IDs
- Pydantic Configuration โ Type-safe settings loaded from environment variables with validation on startup
- Multi-stage Docker Build โ Separate builder and runtime stages; non-root user, minimal final image
- Docker Compose โ Single command local development with volume mounts for live code changes
- Health Endpoint โ
GET /healthreturns server status and version for load balancer checks - Async Tool Handlers โ All tools implemented as async functions safe for concurrent execution
- Graceful Error Responses โ MCP-compliant error objects returned for validation failures and exceptions
Works With
| Client | Supported | Notes |
|---|---|---|
| Claude Desktop | โ | Configure via claude_desktop_config.json with HTTP transport |
| Claude Code | โ | Pass --mcp-server flag with server URL and API key |
| Cursor | โ | Add under Settings โ MCP Servers |
| Windsurf | โ | Add under Cascade MCP configuration |
| Continue | โ | Add as a custom MCP provider in config.json |
Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"fastapi-starter": {
"command": "curl",
"args": ["-s", "http://localhost:8000/mcp"],
"env": {
"MCP_SERVER_URL": "http://localhost:8000",
"MCP_API_KEY": "your-secret-key-here"
}
}
}
}
For HTTP-native MCP clients, point them directly at
http://localhost:8000/mcpwith theAuthorization: Bearer <key>header.
Configuration
| Variable | Description | Required | Default |
|---|---|---|---|
MCP_API_KEY | Secret key clients must send as Bearer token | โ Required | โ |
HOST | Interface the server binds to | Optional | 0.0.0.0 |
PORT | TCP port the server listens on | Optional | 8000 |
LOG_LEVEL | Logging verbosity (DEBUG, INFO, WARNING, ERROR) | Optional | INFO |
LOG_FORMAT | Output format: json for production, console for development | Optional | json |
SERVER_NAME | Display name returned in server info responses | Optional | fastapi-mcp-starter |
SERVER_VERSION | Semantic version returned in server info | Optional | 1.0.0 |
ALLOWED_ORIGINS | Comma-separated CORS origins (e.g. https://app.example.com) | Optional | * |
REQUEST_TIMEOUT | Maximum seconds before a tool call is cancelled | Optional | 30 |
Deployment
Docker (standalone)
# Build the production image
docker build -t mcp-fastapi-starter:latest .
# Run with environment variables
docker run -d \
--name mcp-server \
-p 8000:8000 \
-e MCP_API_KEY=your-strong-secret \
-e LOG_LEVEL=INFO \
mcp-fastapi-starter:latest
Railway
# Install Railway CLI
npm install -g @railway/cli
# Login and create project
railway login
railway init
# Set secrets
railway variables set MCP_API_KEY=your-strong-secret
railway variables set LOG_LEVEL=INFO
# Deploy
railway up
Fly.io
# Install flyctl and login
curl -L https://fly.io/install.sh | sh
fly auth login
# Launch (follow prompts, select Dockerfile)
fly launch
# Set secrets
fly secrets set MCP_API_KEY=your-strong-secret
fly secrets set LOG_LEVEL=INFO
# Deploy
fly deploy
# Check logs
fly logs
Production Checklist
-
MCP_API_KEYis set to a cryptographically random string (minimum 32 characters) -
LOG_FORMATis set tojsonfor machine-parseable logs -
LOG_LEVELis set toINFOorWARNING(notDEBUG) in production - Server is running behind a TLS-terminating reverse proxy (nginx, Caddy, or cloud load balancer)
- Health endpoint (
/health) is configured in your load balancer or container orchestrator - Docker image is built from the
runtimestage only (no build tools in production) - Container runs as non-root user (
appuser) โ verify withdocker inspect -
ALLOWED_ORIGINSis restricted to known client domains (not*) - Resource limits (
--memory,--cpus) are set on the container - Secrets are injected via environment variables or a secrets manager โ never baked into the image
- Log output is shipped to a centralised system (Datadog, Logtail, CloudWatch)
-
REQUEST_TIMEOUTis tuned to match the slowest expected tool execution time - A liveness probe hits
/healthevery 30 seconds with a failure threshold of 3 - The image is regularly rebuilt to pick up base image security patches
- API key rotation procedure is documented for your team
Testing
Manual testing with curl
# List available tools
curl -s -X POST http://localhost:8000/mcp \
-H "Authorization: Bearer your-key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | python -m json.tool
# Call analyze_text
curl -s -X POST http://localhost:8000/mcp \
-H "Authorization: Bearer your-key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"analyze_text","arguments":{"text":"The quick brown fox jumps over the lazy dog.","include_word_frequency":true}}}' | python -m json.tool
# Call transform_json
curl -s -X POST http://localhost:8000/mcp \
-H "Authorization: Bearer your-key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"transform_json","arguments":{"data":{"first_name":"Ada","last_name":"Lovelace","birth_year":1815},"operation":"flatten_keys"}}}' | python -m json.tool
# Verify auth rejection
curl -s -X POST http://localhost:8000/mcp \
-H "Authorization: Bearer wrong-key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/list","params":{}}'
# Expect: 401 Unauthorized
Testing with Claude Desktop
- Start the server:
python server.pyordocker compose up - Open Claude Desktop and go to Settings โ Developer โ Edit Config
- Add the server configuration shown in the Works With section above
- Restart Claude Desktop
- In a new conversation, type: "Use the analyze_text tool to analyse this sentence: The MCP protocol enables powerful AI integrations."
- Claude should invoke the tool and return word count, character count, and frequency data