Skip to main content
The Python template is a production-ready starting point for building MCP servers in Python.

Clone the template

git clone https://github.com/ezforgeai/template-python-mcp-server my-server
cd my-server
pip install -r requirements.txt

Project structure

my-server/
├── src/
│   └── main.py         # MCP server — edit this to add your tools
├── requirements.txt    # Python dependencies
├── Dockerfile          # Production container
└── ezforge.toml        # Deploy configuration

How it works

The template uses the official mcp Python package to implement the Model Context Protocol over HTTP + SSE:
EndpointDescription
GET /healthzHealth check — must return 200
GET /sseMCP clients open a session here
POST /messageMCP clients send tool calls here

Sample server

import os
from mcp.server import Server
from mcp.server.sse import SseServerTransport
from mcp.types import Tool, TextContent, CallToolResult

app = Server("my-mcp-server")

@app.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="echo",
            description="Echoes back whatever message you provide.",
            inputSchema={
                "type": "object",
                "properties": {
                    "message": {"type": "string", "description": "The message to echo."},
                },
                "required": ["message"],
            },
        ),
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> CallToolResult:
    if name == "echo":
        message = arguments["message"]
        return CallToolResult(content=[TextContent(type="text", text=message)])
    raise ValueError(f"Unknown tool: {name}")

Adding tools

1. Declare the tool in list_tools

@app.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="search_docs",
            description="Search documentation by keyword.",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "Search query."},
                    "limit": {"type": "integer", "description": "Max results.", "default": 10},
                },
                "required": ["query"],
            },
        ),
    ]

2. Implement the tool in call_tool

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> CallToolResult:
    if name == "search_docs":
        query = arguments["query"]
        limit = arguments.get("limit", 10)
        results = await search(query, limit)  # your logic here
        return CallToolResult(
            content=[TextContent(type="text", text="\n".join(results))]
        )
    raise ValueError(f"Unknown tool: {name}")

Using environment variables

import os

api_key = os.environ.get("MY_API_KEY")
if not api_key:
    raise RuntimeError("MY_API_KEY environment variable is required")
Set before deploying:
ezforge env set my-server MY_API_KEY=sk-...
ezforge deploy

Local development

# Install dependencies
pip install -r requirements.txt

# Run the server
python src/main.py

# Test health check
curl http://localhost:8080/healthz
# {"status": "ok"}

Build and deploy

ezforge deploy

Dependencies

PackagePurpose
mcpOfficial Python MCP SDK
starletteASGI framework (HTTP/SSE transport)
uvicornASGI server

Dockerfile

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY src/ ./src/
EXPOSE 8080
CMD ["python", "src/main.py"]

Type hints

The Python template is fully typed. Use mypy for static analysis:
pip install mypy
mypy src/