Content
# mcp-py-server-template
A clean, complete boilerplate for building a [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server in Python using [FastMCP](https://gofastmcp.com).
## Features
- Standard `src/` project layout
- Example tools with full type hints and Google-style docstrings
- Example resources (static and parameterized URI templates)
- Example prompts (reusable single- and multi-message templates)
- Context-aware tool (client logging, progress reporting, resource access)
- Environment-based configuration (`pydantic-settings`)
- Structured JSON logging (production) or console logging (dev)
- Selectable transport: `stdio` (local) or `streamable-http` (networked)
- Runnable via `uvx` (no manual environment setup)
- Optional JWT bearer-token authentication for HTTP transports
- Per-tool timeouts and safe error handling
- Optional OpenTelemetry tracing & metrics
- Multi-stage `Dockerfile` (non-root) + `.dockerignore`
- GitHub Actions CI (lint, format, type-check, tests) across Python 3.10–3.12
- Pinned `requirements.lock` for reproducible installs
- Ready-to-run test suite (`pytest` + `pytest-asyncio`)
## Project structure
```
mcp-py-server-template/
├── pyproject.toml # Build config, dependencies, tooling
├── requirements.lock # Pinned versions for reproducible installs
├── Makefile # Common dev tasks (make help)
├── Dockerfile # Multi-stage, non-root production image
├── .dockerignore
├── .env.example # Sample environment configuration
├── README.md
├── .github/workflows/ci.yml # Lint, format, type-check, test pipeline
├── src/
│ └── mcp_server_template/
│ ├── __init__.py # Package exports & version
│ ├── config.py # Env-based settings (pydantic-settings)
│ ├── logging_config.py # JSON / console logging setup
│ ├── auth.py # Optional JWT bearer-token verifier
│ ├── telemetry.py # Optional OpenTelemetry setup
│ ├── server.py # FastMCP app instance + entry point
│ ├── tools.py # Example tools (greet, add, slow_double, process_items)
│ ├── resources.py # Example resources (static + templates)
│ └── prompts.py # Example prompts (reusable templates)
└── tests/
├── __init__.py
├── test_tools.py # Tool tests via the in-memory client
├── test_resources.py # Resource tests via the in-memory client
└── test_prompts.py # Prompt tests via the in-memory client
```
## Configuration
All settings are read from environment variables (prefixed `MCP_`) or a `.env`
file. Copy [.env.example](.env.example) to `.env` and adjust. Key variables:
| Variable | Default | Description |
| --- | --- | --- |
| `MCP_TRANSPORT` | `stdio` | `stdio` or `streamable-http` |
| `MCP_HOST` | `127.0.0.1` | Bind host (HTTP transports) |
| `MCP_PORT` | `8000` | Bind port (HTTP transports) |
| `MCP_LOG_LEVEL` | `INFO` | Logging level |
| `MCP_JSON_LOGS` | `false` | Emit structured JSON logs |
| `MCP_TOOL_TIMEOUT_SECONDS` | `30` | Per-tool timeout |
| `MCP_AUTH_JWKS_URI` | _(unset)_ | JWKS endpoint; enables auth when set |
| `MCP_AUTH_ISSUER` | _(unset)_ | Expected token `iss` claim |
| `MCP_AUTH_AUDIENCE` | _(unset)_ | Expected token `aud` claim |
| `MCP_OTEL_ENABLED` | `false` | Enable OpenTelemetry export |
| `MCP_OTEL_SERVICE_NAME` | `mcp-py-server-template` | Telemetry service name |
## Getting started
Create a virtual environment and install the package in editable mode with the
dev extras:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
For a reproducible install of pinned versions, use the lockfile instead:
```bash
pip install -r requirements.lock
```
## Running the server
Start the server over stdio (the default MCP transport):
```bash
mcp-py-server-template
```
Or run the module directly:
```bash
python -m mcp_server_template.server
```
Run as a networked HTTP service with structured logs:
```bash
MCP_TRANSPORT=streamable-http MCP_HOST=0.0.0.0 MCP_PORT=8000 MCP_JSON_LOGS=true \
mcp-py-server-template
```
## Authentication
For HTTP transports, set the JWKS variables to require valid JWT bearer tokens
(signature, issuer, and audience are verified). Auth is disabled when
`MCP_AUTH_JWKS_URI` is unset — safe for local/stdio use, but **do not** expose
the server publicly without it:
```bash
MCP_TRANSPORT=streamable-http \
MCP_AUTH_JWKS_URI=https://auth.example.com/.well-known/jwks.json \
MCP_AUTH_ISSUER=https://auth.example.com \
MCP_AUTH_AUDIENCE=mcp-py-server-template \
mcp-py-server-template
```
Clients then send `Authorization: Bearer <token>` on each request.
## Observability
Enable OpenTelemetry tracing and metrics (install the extra first):
```bash
pip install -e ".[otel]"
MCP_OTEL_ENABLED=true OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
mcp-py-server-template
```
## Docker
```bash
docker build -t mcp-py-server-template .
docker run -p 8000:8000 mcp-py-server-template
```
The image defaults to the HTTP transport on `0.0.0.0:8000` with JSON logs and
runs as a non-root user. Override any `MCP_*` variable with `-e`.
### Connect from an MCP client
Add the following to your client's MCP configuration (e.g. Claude Desktop,
VS Code). Use an **absolute path** to the interpreter inside your virtual
environment — MCP clients do not inherit your shell's `PATH`, so a bare
command name will fail with `spawn ... ENOENT`:
```json
{
"mcpServers": {
"mcp-py-server-template": {
"command": "/absolute/path/to/mcp-py-server-template/.venv/bin/python",
"args": ["-m", "mcp_server_template.server"]
}
}
}
```
Alternatively, point directly at the generated console script:
```json
{
"mcpServers": {
"mcp-py-server-template": {
"command": "/absolute/path/to/mcp-py-server-template/.venv/bin/mcp-py-server-template"
}
}
}
```
### Run with uvx (recommended)
[`uvx`](https://docs.astral.sh/uv/guides/tools/) (from
[uv](https://docs.astral.sh/uv/)) fetches and runs the package in an ephemeral,
isolated environment. This avoids managing a virtual environment or worrying
about `PATH`, since the client only needs `uvx` on its `PATH`.
Run it locally from the project directory (no publishing required):
```bash
uvx --from . mcp-py-server-template
```
Or run directly from a Git repository:
```bash
uvx --from git+https://github.com/Omar-Emam-99/mcp-py-server-template mcp-py-server-template
```
Once the package is published to PyPI, the source can be omitted entirely:
```bash
uvx mcp-py-server-template
```
Corresponding MCP client configuration:
```json
{
"mcpServers": {
"mcp-py-server-template": {
"command": "uvx",
"args": ["--from", "git+https://github.com/Omar-Emam-99/mcp-py-server-template", "mcp-py-server-template"]
}
}
}
```
After publishing to PyPI, simplify the args to just the package name:
```json
{
"mcpServers": {
"mcp-py-server-template": {
"command": "uvx",
"args": ["mcp-py-server-template"]
}
}
}
```
## Publishing
Build distributables and publish to a package index using
[uv](https://docs.astral.sh/uv/) (targets provided by the [Makefile](Makefile)):
```bash
make build # build sdist + wheel into dist/
make publish-test # upload to TestPyPI
make publish # upload to PyPI
```
Provide credentials via the `UV_PUBLISH_TOKEN` environment variable (or
`uv publish --token ...`). Once published to PyPI, clients can launch the
server with the bare `uvx mcp-py-server-template` form shown above.
## Adding your own tools
Register new tools in [src/mcp_server_template/tools.py](src/mcp_server_template/tools.py)
by decorating a function with `@mcp.tool`. Type hints define the input schema
and the docstring becomes the tool description:
```python
@mcp.tool
def multiply(a: float, b: float) -> float:
"""Multiply two numbers.
Args:
a: The first factor.
b: The second factor.
Returns:
The product of ``a`` and ``b``.
"""
return a * b
```
Tools may be `async` and can be wrapped with `@with_timeout` (see
[tools.py](src/mcp_server_template/tools.py)) to enforce the configured
per-tool timeout and convert failures into safe `ToolError` messages. Validate
inputs at the top of the tool and raise `ToolError` for bad requests.
## Adding your own resources
Resources expose read-only data to clients, addressed by a URI. Register new
resources in [src/mcp_server_template/resources.py](src/mcp_server_template/resources.py)
by decorating a function with `@mcp.resource`. A URI containing `{placeholders}`
becomes a *resource template*, and the matching function parameters are filled
from the requested URI:
```python
@mcp.resource(
"users://{user_id}/profile",
name="user-profile",
description="A synthetic user profile keyed by user id.",
mime_type="application/json",
)
def user_profile(user_id: str) -> str:
"""Return a JSON profile for the given user id."""
return json.dumps({"id": user_id, "display_name": f"User {user_id}"})
```
The template ships with three examples: a static resource
(`config://server-info`) plus two templates (`greeting://{name}` and
`users://{user_id}/profile`). Clients discover them via `list_resources` /
`list_resource_templates` and fetch content with `read_resource`.
## Adding your own prompts
Prompts are reusable message templates. Register new prompts in
[src/mcp_server_template/prompts.py](src/mcp_server_template/prompts.py) by
decorating a function with `@mcp.prompt`. Function parameters become the
prompt's arguments, and the docstring becomes its description. Return a plain
string for a single `user` message, or a list of `Message` objects for a
multi-turn template:
```python
from fastmcp.prompts import Message
@mcp.prompt(name="summarize-text", description="Summarize the provided text.")
def summarize_text(text: str) -> list[Message]:
"""Prime the model to summarize the given text."""
return [
Message("You are a concise summarizer.", role="assistant"),
Message(f"Summarize the following text:\n\n{text}"),
]
```
The template ships with two examples: `explain-topic` (single message with an
optional `level` argument) and `summarize-text` (multi-message). Clients
discover them via `list_prompts` and render them with `get_prompt`.
## Using the request context
Tools, resources, and prompts can access per-request MCP capabilities through
the FastMCP `Context` object. Declare a parameter annotated as `Context` and
FastMCP injects it automatically — clients never supply it, and it does not
appear in the tool's input schema. The context provides structured logging back
to the client, incremental progress reporting, resource access, sampling,
elicitation, and per-request metadata:
```python
from fastmcp import Context
@mcp.tool
async def process_items(count: int, ctx: Context) -> dict[str, object]:
"""Process items while reporting progress and logging to the client."""
await ctx.info(f"Processing {count} items for request {ctx.request_id}")
for index in range(1, count + 1):
await ctx.report_progress(progress=index, total=count)
result = await ctx.read_resource("config://server-info")
return {"processed": count, "request_id": ctx.request_id}
```
The `process_items` tool in
[src/mcp_server_template/tools.py](src/mcp_server_template/tools.py)
demonstrates client logging (`ctx.info` / `ctx.debug`), progress reporting
(`ctx.report_progress`), and reading a server resource (`ctx.read_resource`).
Clients receive the log and progress notifications via the `log_handler` and
`progress_handler` callbacks.
## Development
A [Makefile](Makefile) wraps the common tasks. List all targets with:
```bash
make help
```
Run the checks:
```bash
make check # format-check, lint, type-check, tests
make ci # same checks as GitHub Actions CI
```
Other useful targets: `make install` / `make install-all`, `make sync`
(install from the lockfile), `make lock` (regenerate it), `make format`,
`make lint-fix`, `make run` / `make run-http`, `make docker-build` /
`make docker-run`, and `make clean`.
The equivalent raw commands are:
```bash
ruff format --check . # formatting
ruff check . # lint
mypy src # type-check
pytest -q # tests
```
CI runs the same checks across Python 3.10, 3.11, and 3.12 on every push and
pull request — see [.github/workflows/ci.yml](.github/workflows/ci.yml).
## License
MIT — see [LICENSE](LICENSE).
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available