Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 0 additions & 68 deletions .github/workflows/claude-code-review.yml

This file was deleted.

63 changes: 0 additions & 63 deletions .github/workflows/claude.yml

This file was deleted.

22 changes: 14 additions & 8 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# Manually triggered workflow that creates a release branch, bumps the version
# in VERSION and pyproject.toml, and pushes a tag — which fires the main
# release.yml pipeline.
# in VERSION and pyproject.toml, and pushes a tag — which fires release.yml.
#
# Uses a GitHub App token (MCP_RELEASE_WORKFLOW_APP_ID + MCP_RELEASE_WORKFLOW_APP_KEY secrets) so that the tag
# push triggers downstream workflows. GITHUB_TOKEN cannot do this due to a
# GitHub Actions security restriction.
# Requires a GitHub App token (secrets MCP_RELEASE_WORKFLOW_APP_ID and
# MCP_RELEASE_WORKFLOW_APP_KEY). The default GITHUB_TOKEN cannot fire
# downstream workflows on tag push — GitHub explicitly blocks that to prevent
# infinite workflow recursion.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL: This was the reason for blocking PAT to trigger downstream workflows. 🤗

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep — GITHUB_TOKEN is intentionally barred from triggering downstream workflows to prevent infinite-loop footguns. The GitHub App token dance is the sanctioned escape hatch. 🫠

#
# TODO: This is a template repo — uncomment the workflow_dispatch trigger and
# remove the stub trigger below when using this template in a real project.
# This workflow ships STUBBED so the template repo itself does not cut
# releases. To enable in your own repo:
# 1. Replace the bare `on: workflow_dispatch` trigger below with the richer
# one (with inputs) that is commented out.
# 2. Remove the `if: false` guard on the `create-release` job.
# 3. See `docs/release-playbook.md` for the full list of required secrets.

name: Create Release

# Real trigger — uncomment when enabling releases in your own repo:
# on:
# workflow_dispatch:
# inputs:
Expand All @@ -29,7 +35,7 @@ permissions: {}

jobs:
create-release:
# TODO: Remove this condition when enabling the real trigger above.
# Stub guard — delete this line when enabling the real trigger above.
if: false
runs-on: ubuntu-latest
permissions:
Expand Down
30 changes: 19 additions & 11 deletions .github/workflows/patch-release.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
# Automatically creates a patch release when a PR is merged to a release branch.
# Bumps VERSION and pyproject.toml, commits, tags, and pushes — which triggers
# release.yml.
# Automatically creates a patch release when a PR is merged to a release/X.Y
# branch. Bumps VERSION and pyproject.toml, commits, tags, and pushes — which
# triggers release.yml.
#
# Uses a GitHub App token (MCP_RELEASE_WORKFLOW_APP_ID + MCP_RELEASE_WORKFLOW_APP_KEY secrets) so that the tag
# push triggers downstream workflows. GITHUB_TOKEN cannot do this due to a
# GitHub Actions security restriction.
# Requires a GitHub App token (secrets MCP_RELEASE_WORKFLOW_APP_ID and
# MCP_RELEASE_WORKFLOW_APP_KEY). The default GITHUB_TOKEN cannot fire
# downstream workflows on tag push — GitHub explicitly blocks that to prevent
# infinite workflow recursion.
#
# TODO: This is a template repo — uncomment the pull_request trigger and
# remove the stub trigger below when using this template in a real project.
# This workflow ships STUBBED so the template repo itself does not cut
# releases. To enable in your own repo:
# 1. Replace the `on: workflow_dispatch` trigger below with the
# `on: pull_request` block that is commented out.
# 2. Uncomment the `concurrency` block.
# 3. Replace `if: false` with `if: github.event.pull_request.merged == true`.
# 4. See `docs/release-playbook.md` for the full list of required secrets.

name: Patch Release

# Real trigger — uncomment when enabling releases in your own repo:
# on:
# pull_request:
# types: [closed]
Expand All @@ -20,15 +28,15 @@ on:

permissions: {}

# TODO: Uncomment concurrency when enabling the real trigger above.
# Uncomment when enabling the real trigger above:
# concurrency:
# group: patch-release-${{ github.event.pull_request.base.ref }}
# cancel-in-progress: false

jobs:
patch-release:
# TODO: Remove this condition when enabling the real trigger above,
# and restore: if: github.event.pull_request.merged == true
# Stub guard — replace with `if: github.event.pull_request.merged == true`
# when enabling the real trigger above.
if: false
runs-on: ubuntu-latest
permissions:
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
# Release pipeline: build multi-arch image, sign with Cosign, attest SLSA
# provenance, and create a GitHub Release with auto-generated notes.
#
# This workflow ships STUBBED so the template repo itself does not publish
# releases. To enable in your own repo:
# 1. Replace the `on: workflow_dispatch` trigger with the `on: push: tags`
# block that is commented out below.
# 2. Replace every `PLACEHOLDER` in this file (image name, OCI title, OCI
# description) with your values.
# 3. See `docs/release-playbook.md` for the full list of required secrets.

name: Release

# TODO: Replace the placeholder trigger below with the real one when using this template.
# Replace all occurrences of 'PLACEHOLDER' with the appropriate values.
# Real trigger — uncomment when enabling releases in your own repo:
# on:
# push:
# tags:
Expand Down
51 changes: 51 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Contributing to mcp-template-py

Thanks for your interest! This repo is a template — most contributions will be improvements to the template itself (new MCP tooling patterns, better docs, CI/CD hardening).

## Development setup

```bash
# Prerequisites: Python 3.13+, uv, Task (see README)
task install
cp .env.example .env
task run
```

## Before opening a PR

Run the full check suite locally. It mirrors CI:

```bash
task check # lint + format + typecheck + test + security
```

Individual tasks are listed in `README.md`. All of them must pass before CI will.

## PR conventions

- **Title format:** [Conventional Commits](https://www.conventionalcommits.org/) — enforced by `lint-pr-title.yml`. Examples:
- `feat(tools): add a rate-limited search tool`
- `fix(auth): handle missing Bearer token header`
- `docs: clarify DHI setup`
- `chore: bump dependencies`
- **Scope:** keep PRs focused. Smaller PRs review faster.
- **Tests:** unit tests in `tests/unit/` (alongside the module), integration tests in `tests/integration/`.

## Developer Certificate of Origin (DCO)

All commits must be signed off, certifying that you have the right to submit the contribution:

```bash
git commit -s -m "your message"
```

The `-s` flag appends a `Signed-off-by` trailer. See the [DCO](https://developercertificate.org/) for the full text.

## Reporting issues

- **Bugs / feature requests:** open an issue.
- **Security vulnerabilities:** see [SECURITY.md](SECURITY.md) — do not file a public issue.

## Code of Conduct

By participating in this project, you agree to abide by the [StacklokLabs Code of Conduct](https://github.com/StacklokLabs/.github/blob/main/CODE_OF_CONDUCT.md).
54 changes: 43 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ A production-ready template for building Python MCP (Model Context Protocol) ser

## What's Included

- **FastMCP server** with example tool implementation
- **FastMCP server** with an example tool implementation
- **Token passthrough** — Bearer tokens from MCP clients are available to tools via context. Requests without a Bearer token are rejected with 401 by default (`REQUIRE_BEARER_TOKEN=true`); set to `false` for local development
- **Pydantic** for data validation and type safety
- **Task automation** via [Taskfile](https://taskfile.dev/) for common operations
- **Testing infrastructure** with pytest and pytest-asyncio
- **Code quality tools**: ruff (linting/formatting), ty (type checking)
- **Security scanning**: safety, bandit, pip-audit, cyclonedx-bom
- **Docker support** with multi-platform builds (amd64/arm64) using [Docker Hardened Images (DHI)](https://docs.docker.com/dhi/how-to/use/)
- **GitHub Actions** for CI/CD, code quality, and automated builds (release not included)
- **Security scanning**: bandit, pip-audit, cyclonedx-bom (SBOM), Grype (container + filesystem)
- **Hardened containers** built on [Docker Hardened Images (DHI)](https://docs.docker.com/dhi/) — multi-arch (amd64/arm64)
- **Release pipeline** (shipped as stubs): Cosign signing, SLSA provenance attestation, GitHub Releases with auto-generated notes
- **GitHub Actions** for CI/CD, code quality, and automated builds

## Quick Start

Expand Down Expand Up @@ -45,13 +46,19 @@ task run
task compose
```

> **Note:** Building from source uses [Docker Hardened Images (DHI)](https://docs.docker.com/dhi/how-to/use/)
> which require authentication to `dhi.io`:
> 1. Create a Docker Hub account (or use your existing one)
> 2. Run `docker login dhi.io` (use your Docker Hub credentials)

The server runs on `http://0.0.0.0:8100` by default.

### DHI authentication

The Dockerfile and CI image builds use [Docker Hardened Images (DHI)](https://docs.docker.com/dhi/). DHI is **free** (Community tier, Apache 2.0) but pulls from `dhi.io` require authentication with a Docker Hub account:

```bash
# A free Docker Hub account works — no paid subscription required
docker login dhi.io
```

If you use this template for your own repo, also see [When using this template](#when-using-this-template) below for the GitHub secrets CI needs.

## Implementing New Tools

Tools are implemented in [src/mcp_template_py/api/tools.py](src/mcp_template_py/api/tools.py) as methods on the `Tools` class, and registered in [src/mcp_template_py/api/mcp_builder.py](src/mcp_template_py/api/mcp_builder.py):
Expand Down Expand Up @@ -102,6 +109,8 @@ mcp.add_tool(tools.hello)
| `task format` | Format code and fix lint issues |
| `task typecheck` | Run ty type checker |
| `task test` | Run pytest tests |
| `task security` | Run bandit + pip-audit |
| `task sbom` | Generate a CycloneDX SBOM |
| `task check` | Run all checks (lint, typecheck, test, security) |

## Project Structure
Expand Down Expand Up @@ -137,10 +146,11 @@ Configuration is managed through environment variables. Copy `.env.example` to `
| `MCP_HOST` | `0.0.0.0` | Host for the MCP server to listen on |
| `MCP_PORT` | `8100` | Port for the MCP server to listen on |
| `SERVER_URL` | `http://localhost:8100` | Base URL of the server |
| `REQUIRE_BEARER_TOKEN` | `true` | Reject requests without a Bearer token |

## Testing

**Note**: Integration tests (`tests/integration/`) require the MCP server to be running (`task run` or `task compose`) and will be skipped if unreachable.
Integration tests (`tests/integration/`) require the MCP server to be running (`task run` or `task compose`) and will be skipped if unreachable.

```bash
# Run all tests
Expand All @@ -150,6 +160,28 @@ task test
uv run pytest --cov=src/mcp_template_py
```

## When Using This Template

Whether you created a new repo via "Use this template" or actually forked this one, CI needs a few GitHub Actions secrets to work in your copy:

**Required for image builds and security scans** (`image-build.yml`, `security.yml`):
- `DOCKERHUB_USERNAME` — your Docker Hub username
- `DOCKERHUB_TOKEN` — a [Docker Hub access token](https://docs.docker.com/security/for-developers/access-tokens/) with public-read scope

**Required when you enable the release pipeline** (see [docs/release-playbook.md](docs/release-playbook.md)):
- `MCP_RELEASE_WORKFLOW_APP_ID` — numeric App ID of a GitHub App installed on your repo with Contents: Read/Write
- `MCP_RELEASE_WORKFLOW_APP_KEY` — the App's private key (full `.pem` contents)

The release workflows (`release.yml`, `create-release.yml`, `patch-release.yml`) ship **stubbed** so the template itself does not publish artifacts. To enable them in your copy, follow the unstub steps in [docs/release-playbook.md](docs/release-playbook.md).

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, PR conventions, and the DCO sign-off requirement.

## Security

To report a vulnerability, please use the GitHub Security Advisory flow described in [SECURITY.md](SECURITY.md) — do not file a public issue.

## License

See [LICENSE](LICENSE) for details.
[Apache 2.0](LICENSE).
Loading
Loading