Skip to main content
Glama
JPauloBR

MCP Server Starter

by JPauloBR
README.md
# MCP Server Starter

An opinionated, production-minded Python starter for exposing typed tools over the [Model Context Protocol](https://modelcontextprotocol.io/).

It deliberately uses the stable MCP Python SDK v1 line. SDK v2 is prerelease as of July 2026 and is not yet recommended for production.

![End-to-end MCP server demo](docs/demo.gif)

## What is included

- Streamable HTTP MCP endpoint at `/mcp`
- Typed tool inputs and structured outputs
- FastAPI health and readiness endpoints
- Request IDs and JSON access logs
- Environment-based configuration
- Stateless mode for horizontal scaling
- Unit, protocol, and HTTP tests
- Non-root Docker image
- Clear extension points for gateway authentication and telemetry

## Quick start

Requirements: Python 3.11+ and [uv](https://docs.astral.sh/uv/).

```bash
uv sync --all-groups
uv run uvicorn mcp_server_starter.app:app --reload
```

The service exposes:

- MCP: `http://localhost:8000/mcp`
- Liveness: `http://localhost:8000/health/live`
- Readiness: `http://localhost:8000/health/ready`

Try it with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):

```bash
npx @modelcontextprotocol/inspector
```

Use `http://localhost:8000/mcp` as the Streamable HTTP server URL.

Or run the included demo client in a second terminal:

```bash
uv run python scripts/demo_client.py
```

It discovers the available tools and calls `echo` over the Streamable HTTP endpoint.

## Included tools

- `echo`: returns a validated message and request correlation ID
- `service_status`: returns service name, version, and health state

They are intentionally boring. Replace them with domain tools while keeping the surrounding operational shell.

## Configuration

All settings use the `MCP_STARTER_` prefix.

| Variable | Default | Purpose |
|---|---|---|
| `MCP_STARTER_ENVIRONMENT` | `development` | Deployment environment name |
| `MCP_STARTER_LOG_LEVEL` | `INFO` | Python log level |
| `MCP_STARTER_ALLOWED_ORIGINS` | `[]` | JSON array of browser origins allowed by CORS |

Example:

```bash
export MCP_STARTER_ENVIRONMENT=production
export MCP_STARTER_ALLOWED_ORIGINS='["https://your-mcp-client.example"]'
```

## Architecture

```text
MCP client
    |
identity-aware API gateway   <- authenticate, authorize, rate-limit
    |
FastAPI operational shell    <- health, request IDs, access logs
    |
MCP Streamable HTTP          <- protocol and typed schemas
    |
domain tools                 <- business authorization + services
    |
logs, traces, metrics, audit events
```

This starter does not pretend that a hard-coded API key is enterprise authentication. Terminate OAuth/OIDC at an identity-aware gateway, pass only trusted identity context to the application, and still enforce resource-level authorization inside each tool.

## Development

```bash
uv run pytest
uv run ruff check .
uv run ruff format --check .
```

Build and run the container:

```bash
docker build -t mcp-server-starter .
docker run --rm -p 8000:8000 mcp-server-starter
```

## Production checklist

- Put the service behind TLS and an identity-aware gateway.
- Restrict CORS to explicit origins; an empty list is the safe default.
- Add tool-level authorization—not only endpoint authentication.
- Emit audit events for every side-effecting tool.
- Add OpenTelemetry export for your chosen backend.
- Set timeouts, retries, and circuit breakers around downstream calls.
- Pin dependencies through `uv.lock` and scan the image in CI.
- Treat tool descriptions and schemas as part of your public API.

## Releasing

Releases are tag-driven and use PyPI trusted publishing—no long-lived API token is stored in
GitHub. Before the first release, configure a PyPI trusted publisher for this repository with
workflow `release.yml` and environment `pypi`.

1. Update the version in `pyproject.toml` and merge the change after CI passes.
2. Create and push the matching tag, for example `v0.1.0`.
3. The release workflow verifies the tag/version pair, runs lint and tests, builds and validates
   the wheel and source distribution, publishes them to PyPI, and creates a GitHub release with
   generated notes and attached artifacts.

The workflow can also be run manually to validate the complete build without publishing.

## Author

[JP Oliveira](https://jpaulo.io) · [LinkedIn](https://www.linkedin.com/in/jpaulobr/)