> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ezforge.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Python Template

> Build and deploy a Python MCP server with the official SDK

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

## Clone the template

```bash theme={null}
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:

| Endpoint        | Description                      |
| --------------- | -------------------------------- |
| `GET /healthz`  | Health check — must return `200` |
| `GET /sse`      | MCP clients open a session here  |
| `POST /message` | MCP clients send tool calls here |

## Sample server

```python theme={null}
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`

```python theme={null}
@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`

```python theme={null}
@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

```python theme={null}
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:

```bash theme={null}
ezforge env set my-server MY_API_KEY=sk-...
ezforge deploy
```

## Local development

```bash theme={null}
# 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

```bash theme={null}
ezforge deploy
```

## Dependencies

| Package     | Purpose                             |
| ----------- | ----------------------------------- |
| `mcp`       | Official Python MCP SDK             |
| `starlette` | ASGI framework (HTTP/SSE transport) |
| `uvicorn`   | ASGI server                         |

## Dockerfile

```dockerfile theme={null}
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:

```bash theme={null}
pip install mypy
mypy src/
```
