Skip to content
Merged
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
60 changes: 26 additions & 34 deletions docs/documentation/RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,45 @@
# 📦 Releasing PrintMCP to PyPI

PrintMCP publishes to [PyPI](https://pypi.org/project/printmcp/) automatically when a version tag
is pushed, using **[Trusted Publishing](https://docs.pypi.org/trusted-publishers/)** (OIDC) — no
API tokens or passwords are stored anywhere. The workflow is
[`.github/workflows/release.yml`](../.github/workflows/release.yml).
is pushed, using a scoped **PyPI API token** stored as the `PYPI_API_TOKEN` repo secret. The workflow
is [`.github/workflows/release.yml`](../.github/workflows/release.yml).

This page has two parts: a **one-time setup** (done once, on the PyPI website) and the
**per-release** steps (a couple of commands).
This page has two parts: a **one-time setup** (done once) and the **per-release** steps (a couple
of commands).

---

## One-time setup (maintainer, on pypi.org)

You only do this once. It tells PyPI to trust releases coming from this repo's GitHub Actions.
## One-time setup (maintainer)

### 1. Create the GitHub environment

In the GitHub repo: **Settings → Environments → New environment**, name it exactly **`pypi`**.
(Optional but recommended: add yourself as a required reviewer so each publish needs a click.)

### 2. Register the Trusted Publisher on PyPI
### 2. Generate a PyPI API token

Because `printmcp` doesn't exist on PyPI yet, use a **pending publisher**:
1. Sign in at <https://pypi.org> as [sourceboxai](https://pypi.org/user/sourceboxai/).
2. Go to **Account settings → API tokens → Add API token**.
3. Scope the token to the **`printmcp`** project (not "Entire account").
4. Copy the token — it starts with `pypi-`.

1. Sign in at <https://pypi.org> (create an account if needed).
2. Go to **<https://pypi.org/manage/account/publishing/>**.
3. Under **Add a new pending publisher → GitHub**, fill in **exactly**:
### 3. Add the token as a GitHub secret

| Field | Value |
|-------|-------|
| PyPI Project Name | `printmcp` |
| Owner | `SourceBox-LLC` |
| Repository name | `PrintMCP` |
| Workflow name | `release.yml` |
| Environment name | `pypi` |
In the GitHub repo: **Settings → Secrets and variables → Actions → New repository secret**:

4. Click **Add**.
| Field | Value |
|-------|-------|
| Name | `PYPI_API_TOKEN` |
| Secret | *(paste the `pypi-...` token)* |

> [!NOTE]
> A *pending* publisher doesn't reserve the name — it's claimed on the first successful publish.
> The project name must match `name` in [`pyproject.toml`](../pyproject.toml) (PyPI treats `-` and
> `_` as equivalent). After the first release it becomes a normal publisher; no further setup.
Or via the CLI:

That's it. No secrets are added to GitHub — OIDC handles authentication at publish time.
```bash
gh secret set PYPI_API_TOKEN --repo SourceBox-LLC/PrintMCP
# paste the token when prompted
```

That's it — the workflow reads `PYPI_API_TOKEN` at publish time.

---

Expand Down Expand Up @@ -85,7 +82,7 @@ Pushing the `v*` tag triggers [`release.yml`](../.github/workflows/release.yml),
1. verifies the tag matches the package version,
2. builds the sdist + wheel with `uv build`,
3. validates metadata with `twine check`,
4. publishes to PyPI via Trusted Publishing.
4. publishes to PyPI using `PYPI_API_TOKEN`.

Watch it under the repo's **Actions** tab (or `gh run watch`). When it's green, the new version is
live at <https://pypi.org/project/printmcp/>.
Expand All @@ -112,20 +109,15 @@ uvx twine check dist/*

Both artifacts should report `PASSED`.

To rehearse the *whole* publish flow against a throwaway index, add a
[TestPyPI](https://test.pypi.org) pending publisher with the same details, then temporarily point
the publish step at it with `with: { repository-url: https://test.pypi.org/legacy/ }`. Remove that
before publishing for real.

---

## Troubleshooting

| Symptom | Cause / fix |
|---------|-------------|
| `Tag vX does not match pyproject version` | Bump `version` in `pyproject.toml` (or retag). They must be equal. |
| Publish step: `not authorized` / OIDC error | The pending publisher details don't match. Re-check owner `SourceBox-LLC`, repo `PrintMCP`, workflow `release.yml`, environment `pypi`. |
| Publish step: `InvalidDistribution` / 403 | The `PYPI_API_TOKEN` secret is missing, expired, or scoped to the wrong project. Regenerate and re-add it. |
| `File already exists` | That version was already published. PyPI is immutable — bump to a new version. |
| Workflow didn't trigger | The tag must start with `v` (e.g. `v0.2.0`) and be pushed (`git push origin v0.2.0`). |

More: [PyPI Trusted Publishers docs](https://docs.pypi.org/trusted-publishers/).
More: [PyPI API tokens docs](https://docs.pypi.org/trusted-publishers/).