A production-ready Python scaffold for building MCP servers with FastMCP.
MCP Server Template
A generic, production-ready scaffold for building Model Context Protocol (MCP) servers with Python and FastMCP.
This template preserves the architecture, patterns, and best practices of a real production MCP server — stripped of all domain-specific code so you can fork it and build your own.
It also serves as an onboarding project and a reference codebase for coding agents (e.g. Claude, Cursor, Copilot). The structure, inline annotations, and documentation are intentionally designed so that an AI agent can read the codebase, understand the conventions, and rapidly scaffold new tools, workflows, and packages without human hand-holding.
Architecture
mcp-template/
├── packages/
│ ├── equator/ # Prompt-toolkit TUI foundation — base layer for all terminal UIs
│ ├── beetle/ # Live log interpreter — ingests logs, explains them with a local LLM
│ ├── tropical/ # MCP protocol inspector — browse tools, resources, and prompts
│ ├── lab_mouse/ # Pydantic-AI agent REPL — tests whether the LLM uses your tools correctly
│ └── mcp_shared/ # Shared utilities (response builders, schemas, logging)
├── mcp_server/ # Main MCP Server
│ └── src/mcp_server/
│ ├── __main__.py # Server entry point
│ ├── instructions/ # Agent instructions (4-layer framework)
│ ├── tool_box/ # Tool registration + _tools_template reference
│ └── workflows/ # Multi-step workflow orchestration
├── tests/
│ ├── unit/ # Unit tests for packages
│ └── agentic/ # Agentic integration tests (requires running server)
└── docs/ # Architecture and best practices documentation
Key Design Decisions
mcp_shared— All tools use shared response builders (SummaryResponse,ErrorResponse) andResponseFormatenum to control output verbosity and token usage._tools_template— A fully annotated reference implementation. Every architectural decision is documented inline. Read this before creating your first tool.- Docstring Registry — Tool descriptions are versioned separately from logic, enabling A/B testing and prompt engineering without touching business logic.
- ToolNames Registry — All tool names are constants. No inline strings — prevents typos and enables safe IDE refactors.
Setup
Prerequisites
- Python 3.13+
- uv package manager
Install
git clone <your-repo-url> mcp-template
cd mcp-template
uv sync --all-packages
Configure environment
cp .env.sample .env
# Edit .env with your settings (no required values for basic local usage)
Running the Server
uv run mcp_server
The server starts at http://127.0.0.1:8000/mcp/ (streamable HTTP transport).
Health check
curl http://127.0.0.1:8000/healthcheck
# → OK
Developer Toolchain
The template ships three tools for iterating on your MCP server without leaving the terminal.
equator — TUI foundation
The shared prompt-toolkit base that beetle and lab_mouse are built on. Not a standalone tool — a library. Use it directly if you want to wrap your own pydantic-ai agent in a full terminal interface:
import equator
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStreamableHTTP
agent = Agent("openai:gpt-4o", toolsets=[MCPServerStreamableHTTP("http://localhost:8000/mcp")])
equator.run(agent, name="my-tester")
The lower layers (protocol.py, state.py, components/) have no pydantic-ai dependency — any async backend that implements SessionProtocol can drive the TUI. beetle uses this to run a completely different session type with the same rendering infrastructure.
Custom commands are registered via CommandRegistry and passed to equator.run(). Three kinds: ACTION (executes TUI-side logic), PROMPT (pre-fills the input box), SCRIPT (sends a fixed message to the agent).
See `packages/equator/README.md` for the full reference.
beetle — live log interpreter
Wraps any Python process with a full-screen TUI that ingests logs over TCP and interprets them in plain language using a local LLM. No API keys required — runs on Ollama.
uv run beetle # listens on localhost:9020
Wire your application with the built-in handler:
from beetle.log_server import BeetleHandler
import logging
logging.getLogger().addHandler(BeetleHandler())
Or use the zero-dependency snippet if you don't want beetle as a project dependency:
import json, socket, logging
class BeetleHandler(logging.Handler):
def __init__(self, host="localhost", port=9020):
super().__init__()
self._sock = socket.create_connection((host, port))
def em
Environment Variables
NONENo required environment variables for basic local usage.Configuration
{"mcpServers": {"mcp-template": {"command": "uv", "args": ["run", "mcp_server"], "cwd": "/path/to/mcp-template"}}}