One-time operator steps required before the release pipeline can publish to PyPI. All three packages (authplane-sdk, authplane-mcp, authplane-fastmcp) must be configured independently.
For each of the three package names, on pypi.org:
- Sign in as an account that owns (or will own) the package name.
- Account settings → Publishing → Add a new pending publisher:
- Project name:
authplane-sdk(repeat forauthplane-mcp,authplane-fastmcp). - Owner:
AuthPlane - Repository name:
python-sdk - Workflow name:
release.yml - Environment name:
pypi
- Project name:
- Save.
The first successful release to each package name converts the pending publisher into a permanent trusted publisher.
No API tokens are stored in GitHub. Authentication uses short-lived OIDC tokens scoped to the specific workflow + environment.
In the AuthPlane/python-sdk repository:
- Settings → Environments → New environment →
pypi. - (Optional) Add required reviewers so every release waits for a human approval before uploading to PyPI.
- (Optional) Restrict the environment to tags matching
v*.*.*so only the tag-triggeredpublish-pypi.ymlworkflow can deploy. The release tag is pushed directly onto therelease/v*/hotfix/v*source branch (no merge to the default branch);publish-pypi.ymlruns on the tag push and publishes to PyPI via OIDC.
The release workflow reads CHANGELOG.md for notes. Ensure:
- Every release has a
## [X.Y.Z]heading on the source branch (release/v*orhotfix/v*) before running the release workflow. - The default branch always carries
## [Unreleased]between releases. Thecut-releaseworkflow enforces this onrelease/v*cuts (refuses to cut if missing);hotfix/v*cuts skip the check because they branch off an older tag.
PyPI does not support atomic multi-package uploads. If the release workflow publishes one or two packages then fails:
- Download the build artifact from the failed workflow run (named
dist-vX.Y.Z). It preserves the repo's directory structure:dist/,authplane-mcp/dist/,authplane-fastmcp/dist/. - For each package still missing from PyPI, authenticate with
twine(API token or another trusted-publisher call) and run the matching upload:# Root package twine upload dist/* # MCP adapter twine upload authplane-mcp/dist/* # FastMCP adapter twine upload authplane-fastmcp/dist/*
- Manually create the GitHub Release if that step was also skipped:
No
gh release create vX.Y.Z --title vX.Y.Z --notes-file <path-to-notes>
--target— the tag already points at the correct commit on the (now deleted) source branch. - If any commits on the source branch need to reach the default branch, dispatch the Backport fixes workflow with
fromBranch=vX.Y.Z(the tag, not the branch — the branch was deleted after the atomic push).
The git tag is already live, so re-running the workflow is not an option (tag-exists pre-flight will refuse).
authplane-mcp/pyproject.toml declares "mcp @ git+https://github.com/modelcontextprotocol/python-sdk.git@main" as a direct VCS reference (enabled via [tool.hatch.metadata] allow-direct-references = true). PyPI rejects uploads containing direct-URL references, so the first attempt to publish authplane-mcp via this workflow will fail at the upload step. Before cutting the first real release, replace that dependency with a pinned published version of mcp (e.g., "mcp>=X.Y"). The workflow builds and twine checks both will pass; only the actual upload will reject. Dry-run (dryRun: true) masks this by skipping the upload.