diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 33a75aa..3e2d32c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,2 @@ -# CODEOWNERS — auto-assigns @heznpc to every PR for review. -# Solo-dev repo; this exists so Dependabot's grouped bump PRs and outside -# contributions land in the right inbox without manual triage. +# Solo-dev: route all PRs to the maintainer. * @heznpc diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 261c77e..c5e870c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,7 +7,7 @@ - [ ] `ruff check .` passes - [ ] `ruff format --check .` passes - [ ] `mypy src/` passes -- [ ] `pytest` passes (coverage >= 70%) +- [ ] `pytest` passes locally; CI coverage gate (see `ci.yml`) will enforce the threshold - [ ] CHANGELOG.md `[Unreleased]` updated if user-visible - [ ] No `.coverage`, `.env`, secrets, or build artifacts committed diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 1cd3d5f..c7f2b61 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -16,9 +16,9 @@ jobs: runs-on: ubuntu-latest environment: pypi permissions: - contents: write # softprops/action-gh-release needs this to create the Release - id-token: write # OIDC for PyPI trusted publishing + sigstore attestations - attestations: write # actions/attest-build-provenance writes to the repo's attestation store + contents: write + id-token: write + attestations: write steps: - uses: actions/checkout@v6 with: @@ -27,6 +27,8 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.13" + cache: pip + cache-dependency-path: pyproject.toml - name: Extract version id: pkg @@ -48,19 +50,15 @@ jobs: python -m build - name: Generate SLSA build provenance attestation - # Produces a sigstore-signed attestation linking the GitHub Actions run - # to the built wheel + sdist. Surfaces on the PyPI release page as - # "Build attestations: verified" once PyPI ingests it. uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 with: subject-path: 'dist/*' - name: Publish to PyPI - # Pinned to floating `release/v1` per pypa guidance — the action - # self-updates within v1 and pypa rotates secrets behind that ref. + # Floating `release/v1` is pypa's documented pin; the action self-updates within v1. + # OIDC trusted publishing — configure at: + # https://pypi.org/manage/project/my-mcp-server/settings/publishing/ uses: pypa/gh-action-pypi-publish@release/v1 - # Uses OIDC trusted publishing — no PYPI_TOKEN secret needed. - # Configure at: https://pypi.org/manage/project/my-mcp-server/settings/publishing/ with: attestations: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29e872d..d5881ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,8 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.13" + cache: pip + cache-dependency-path: pyproject.toml - name: Install dependencies run: pip install -e . pip-licenses @@ -66,6 +68,8 @@ jobs: - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: pyproject.toml - name: Install dependencies run: pip install -e ".[dev]" @@ -80,6 +84,4 @@ jobs: run: mypy src/ - name: Test (with coverage) - # --cov / --cov-fail-under live in pyproject.toml [tool.pytest.ini_options] - # so local `pytest` invocations match CI exactly. - run: pytest -v + run: pytest -v --cov=my_mcp_server --cov-report=term-missing --cov-fail-under=70 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8434c8b..44fea7c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -18,10 +18,8 @@ jobs: strategy: fail-fast: false matrix: - # `python` scans src/ for code-level issues. # `actions` scans .github/workflows/ for workflow-level issues - # (untrusted input in `run:`, script injection, missing permissions, - # etc.). Available since CodeQL 2.20. + # (script injection, missing permissions, etc.). language: [python, actions] steps: - uses: actions/checkout@v6 @@ -32,8 +30,7 @@ jobs: languages: ${{ matrix.language }} - name: Autobuild - # `actions` language doesn't need a build step but autobuild is a no-op - # there, so leave it for simplicity. + if: matrix.language == 'python' uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis diff --git a/.python-version b/.python-version deleted file mode 100644 index 24ee5b1..0000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.13 diff --git a/SECURITY.md b/SECURITY.md index 56a5ad0..24a0da6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,13 +2,6 @@ ## Reporting a Vulnerability -If you discover a security vulnerability, please report it responsibly: - -1. **Do NOT** open a public issue -2. Email the maintainer or use GitHub's private vulnerability reporting - -## Reporting - Use [GitHub's private vulnerability reporting](https://github.com/starter-series/python-mcp-server-starter/security/advisories/new) (Security tab → Report a vulnerability). Do **not** open a public issue. diff --git a/pyproject.toml b/pyproject.toml index a001a99..bde7237 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,9 +29,7 @@ dependencies = [ my-mcp-server = "my_mcp_server.server:main" [project.optional-dependencies] -# Dev tools are major-bounded so a future breaking release of ruff/mypy/pytest -# can't silently turn CI red. Dependabot's `lint` / `test` groups bump the -# upper bound when a new major ships and we've reviewed the changelog. +# Major-bounded so a breaking ruff/mypy/pytest release can't turn CI red overnight. dev = [ "pytest>=8,<10", "pytest-asyncio>=1,<2", @@ -45,15 +43,10 @@ target-version = "py311" line-length = 100 [tool.ruff.lint] -# E/F/W/I/N/UP — standard correctness + style + import order + pep8-naming + pyupgrade. -# B — flake8-bugbear: common Python bugs (mutable defaults, etc.). -# S — flake8-bandit: security antipatterns (subprocess shell=True, etc.). -# ASYNC — async-specific antipatterns (blocking calls in async funcs). -# RUF, SIM — ruff-specific + simplifications. select = ["E", "F", "I", "N", "W", "UP", "B", "S", "ASYNC", "RUF", "SIM"] [tool.ruff.lint.per-file-ignores] -# pytest's primary API is `assert` — S101 fires on every test line otherwise. +# pytest's primary API is `assert`. "tests/**" = ["S101"] [tool.mypy] @@ -72,9 +65,8 @@ disallow_untyped_defs = false [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] -# --cov-fail-under matches the baseline measured at sweep time (~71%). Bump -# this number when coverage improves — never lower it without justification. -addopts = "--cov=my_mcp_server --cov-report=term-missing --cov-fail-under=70" +# Coverage flags live in ci.yml, not here — so `pytest tests/test_one.py` for a +# quick local check doesn't pay the instrumentation tax or fail on --cov-fail-under. [tool.coverage.run] branch = true