Skip to main content
Glama
guilherme-ads

mcp-server-template

mcp-server-template

Template repository for building MCP servers in Python with FastMCP.

It ships a working server with one example of each building block (tool, resource, prompt, service, schema) so you can clone it, delete the examples, and start writing your own features immediately.

Design goals: simple, organized, low coupling. server.py only assembles the server — everything else lives behind explicit register_* functions.

Requirements

  • Python 3.12+

  • uv

Related MCP server: Python MCP Server Template

Installation

uv sync

That creates .venv, installs runtime + dev dependencies, and installs the project itself (so import mcp_server works with the src/ layout — no sys.path hacks).

Optional configuration:

cp .env.example .env

Running

STDIO (default — for local clients like Claude Desktop)

uv run fastmcp run src/mcp_server/server.py:mcp

HTTP

uv run fastmcp run src/mcp_server/server.py:mcp \
  --transport http \
  --host 0.0.0.0 \
  --port 8000

Using the settings from .env

The installed entry point reads MCP_TRANSPORT, MCP_HOST and MCP_PORT:

uv run mcp-server

Tests

uv run pytest

Tests drive the server in memory through fastmcp.Client(server) — no HTTP server, no subprocess. The client fixture in tests/conftest.py does the wiring:

async def test_echo(client):
    result = await client.call_tool("echo", {"message": "hello"})
    assert result.data.result == "hello"

Quality

uv run ruff check .     # lint
uv run ruff format .    # format

Project structure

mcp-server-template/
├── src/
│   └── mcp_server/
│       ├── server.py            # assembly only: create_server() + mcp
│       ├── config.py            # pydantic-settings (MCP_* env vars)
│       ├── tools/               # register_tools(mcp)
│       ├── resources/           # register_resources(mcp)
│       ├── prompts/             # register_prompts(mcp)
│       ├── services/            # business logic, MCP-agnostic
│       └── schemas/             # pydantic models
├── tests/
├── .env.example
├── Dockerfile
├── pyproject.toml
└── README.md

The flow is always the same:

tool/resource/prompt  ->  service  ->  schema
   (MCP surface)         (logic)      (data)

Configuration

Settings come from environment variables (or .env) via pydantic-settings. Each field maps to an MCP_-prefixed variable:

Variable

Default

Description

MCP_SERVER_NAME

mcp-server-template

Name advertised to clients

MCP_TRANSPORT

stdio

stdio, http or sse

MCP_HOST

127.0.0.1

HTTP host

MCP_PORT

8000

HTTP port

MCP_LOG_LEVEL

INFO

Log level

Add a new setting by adding a typed field to Settings in src/mcp_server/config.py and documenting it in .env.example.

How to create a new tool

  1. Create src/mcp_server/tools/my_feature.py:

from fastmcp import FastMCP

from mcp_server.services.my_service import do_the_work


def register_my_feature_tool(mcp: FastMCP) -> None:
    @mcp.tool
    def my_feature(query: str, limit: int = 10) -> list[str]:
        """One-line description the model will read.

        Args:
            query: What to look for.
            limit: Maximum number of results.
        """
        return do_the_work(query, limit=limit)
  1. Register it in src/mcp_server/tools/__init__.py:

from mcp_server.tools.my_feature import register_my_feature_tool


def register_tools(mcp: FastMCP) -> None:
    register_my_feature_tool(mcp)

Notes:

  • The docstring is the tool description sent to the model — write it for the model.

  • Return a Pydantic model (see schemas/) when the output has structure.

  • Keep the logic in a service; the tool stays a thin adapter.

  • Use async def only when the work is actually I/O-bound (HTTP calls, DB, etc.).

How to create a resource

# src/mcp_server/resources/my_resource.py
from fastmcp import FastMCP


def register_my_resource(mcp: FastMCP) -> None:
    @mcp.resource("data://items", mime_type="application/json")
    def items() -> list[dict[str, str]]:
        """Static resource: fixed URI."""
        return [{"id": "1", "name": "example"}]

    @mcp.resource("data://items/{item_id}")
    def item(item_id: str) -> dict[str, str]:
        """Resource template: the URI carries a parameter."""
        return {"id": item_id, "name": "example"}

Then add register_my_resource(mcp) to register_resources in resources/__init__.py.

How to create a prompt

# src/mcp_server/prompts/my_prompt.py
from fastmcp import FastMCP


def register_my_prompt(mcp: FastMCP) -> None:
    @mcp.prompt
    def review_code(code: str, language: str = "python") -> str:
        """Ask the model to review a snippet."""
        return f"Review this {language} code and list concrete issues:\n\n{code}"

Then add register_my_prompt(mcp) to register_prompts in prompts/__init__.py.

Adding an external integration

Put the client in services/ (e.g. services/github_service.py), read credentials from config.py, and keep the tool as a thin wrapper. The service never imports FastMCP, so it stays unit-testable on its own.

Removing the examples

The echo example is self-contained. To drop it:

rm src/mcp_server/tools/echo.py \
   src/mcp_server/resources/server_info.py \
   src/mcp_server/prompts/summarize.py \
   src/mcp_server/services/echo_service.py \
   src/mcp_server/schemas/echo.py \
   tests/test_tools_echo.py \
   tests/test_services_echo.py \
   tests/test_resources_and_prompts.py

Then remove the matching import + call in tools/__init__.py, resources/__init__.py and prompts/__init__.py, and drop the capability assertions in tests/test_server.py.

Docker

docker build -t mcp-server-template .
docker run --rm -p 8000:8000 mcp-server-template

The image runs the HTTP transport on port 8000 (MCP_TRANSPORT=http, MCP_HOST=0.0.0.0). Pass overrides with -e, e.g. docker run --rm -p 8000:8000 -e MCP_SERVER_NAME=my-server ....

MCP client configuration

Claude Code

The repo ships a project-scoped .mcp.json: open the project in Claude Code and approve the server when prompted. Or register it yourself:

claude mcp add my-server -- uv run --directory /absolute/path/to/mcp-server-template   fastmcp run src/mcp_server/server.py:mcp

The bundled .mcp.json uses a relative path, so it assumes the client launches the server from the project root. If yours doesn't, add --directory /absolute/path after run.

Codex CLI

Codex uses TOML, not JSON. In ~/.codex/config.toml (or a project-scoped .codex/config.toml):

[mcp_servers.my-server]
command = "uv"
args = ["run", "--directory", "/absolute/path/to/mcp-server-template",
        "fastmcp", "run", "src/mcp_server/server.py:mcp"]

[mcp_servers.my-server.env]
MCP_SERVER_NAME = "my-server"

Or via CLI:

codex mcp add my-server -- uv run --directory /absolute/path/to/mcp-server-template   fastmcp run src/mcp_server/server.py:mcp

Generic STDIO (other clients)

{
  "mcpServers": {
    "my-server": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/mcp-server-template",
        "fastmcp",
        "run",
        "src/mcp_server/server.py:mcp"
      ],
      "env": {
        "MCP_SERVER_NAME": "my-server"
      }
    }
  }
}

HTTP (server already running)

{
  "mcpServers": {
    "my-server": {
      "url": "http://localhost:8000/mcp"
    }
  }
}

Codex equivalent:

[mcp_servers.my-server]
url = "http://localhost:8000/mcp"

On Windows, uv must be resolvable by the client process. It lives in %USERPROFILE%\.localin; if the client can't find it, use the absolute path to uv.exe as command.

License

No license file is included on purpose — add the one your project needs.

Available Tools

1 tool
echoEchoA

Echo a message back, optionally repeated.

ParametersJSON Schema
NameRequiredDescriptionDefault
timesNoHow many times to repeat it (1-10).
messageYesText to echo back.

Output Schema

ParametersJSON Schema
NameRequiredDescription
timesYesHow many times the message was repeated.
resultYesThe repeated message.
messageYesThe original message.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry behavioral meaning; 'echo back' clearly communicates a non-mutating pass-through operation and 'optionally repeated' adds the only behavioral variant. It does not explicitly state the absence of state changes, but the echo semantics strongly imply it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is eight words long and front-loads the core operation, followed by the optional qualifier. Every word contributes meaning with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

This is a minimal, single-purpose tool with fully documented parameters, no siblings, and an output schema present. The description, combined with the schema, is sufficient for an agent to select and call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the parameter semantics already live in the input schema. The description's 'optionally repeated' maps to the times parameter but adds no range, formatting, or ordering details beyond what the schema already states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names the exact operation ('Echo a message back') and the object it operates on (a message), and adds the optional repetition behavior. With no sibling tools to differentiate, this is fully specific and unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context that this tool simply returns a provided message, optionally repeated. There are no siblings or exclusions to document, so the intended use is evident from the purpose statement, though no explicit when/when-not guidance is given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.1.0
    • First observedecho

TDQS

A3.8/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no possibility of ambiguity or misselection. The tool name 'echo' is clear and the single purpose is obvious.

Naming Consistency3/5

There is only one tool, so no pattern can be established. The name itself is a clean, descriptive verb, but consistency cannot be meaningfully assessed with a single instance.

Tool Count1/5

The server exposes a single trivial tool ('echo'), which is far too thin to serve any meaningful MCP workflow. As per calibration, a single trivial tool warrants the lowest score.

Completeness1/5

The tool surface is severely incomplete for any practical domain. Even as a template, offering only an echo tool provides essentially no functionality that an agent could build upon.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A basic MCP server template that provides a foundation for building custom tools, resources, and prompts. Serves as a starting point for developers to create their own MCP server functionality.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    A foundational template for building MCP servers in Python using Streamable HTTP transport. Provides example implementations of tools, resources, and prompts to help developers create custom MCP integrations for AI assistants.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    A bare-bones FastMCP server template designed to serve as a starting point for building custom Model Context Protocol servers. It provides a foundational structure for implementing tools over HTTP and includes a built-in health check utility.
    GPL 3.0
  • F
    license
    Not graded
    quality
    D
    maintenance
    A production-ready Python scaffold for building Model Context Protocol (MCP) servers using FastMCP. It provides a structured framework for developers and AI agents to rapidly develop, test, and manage custom tools and workflows.
    1
    -