This guide is for maintainers publishing google-ads-mcp to TestPyPI, PyPI,
and GitHub Releases. The release workflow uses PyPI Trusted Publishing and
short-lived OpenID Connect (OIDC) credentials. Do not create a PYPI_TOKEN or
store a PyPI password in GitHub.
The workflow file name and GitHub environment names are part of the trusted identity. If any of them changes, update the publisher configuration on the corresponding package index before attempting another publication.
The maintainer performing the one-time setup needs:
- repository administration access for
googleads/google-ads-mcp; - Owner or Maintainer access to
google-ads-mcpon PyPI; and - access to the corresponding project on TestPyPI, or permission to create a pending publisher there.
In the repository, open Settings > Environments and create these two environments:
pypifor production releases; andtestpypifor manual rehearsals.
For pypi, configure at least one required reviewer and restrict deployments
to tags matching v*. Do not allow administrators to bypass the production
approval unless the repository's incident procedure explicitly requires it.
Required reviewers are optional for testpypi, but the environment must still
exist because its name is included in the OIDC identity.
Follow PyPI's guide for adding a publisher to an existing project:
-
Sign in to PyPI and open Your projects.
-
Select Manage for
google-ads-mcp. -
Open Publishing.
-
Add a GitHub Actions publisher with these exact values:
Field Value Owner googleadsRepository name google-ads-mcpWorkflow name release.ymlEnvironment name pypi
After registration, the publish-pypi job can request a short-lived token.
The job grants id-token: write only for that publication and uses PyPA's
official Trusted Publishing
action.
Sign in to TestPyPI and register a separate GitHub Actions publisher. Use the same values as production except for the environment:
| Field | Value |
|---|---|
| Owner | googleads |
| Repository name | google-ads-mcp |
| Workflow name | release.yml |
| Environment name | testpypi |
PyPI and TestPyPI are separate services. Registering the production publisher does not configure TestPyPI.
The manual workflow is available after release.yml has been merged into the
repository's default branch.
- Open Actions > Release > Run workflow.
- Select the revision to test and start the workflow.
- Approve the
testpypienvironment if reviewers are configured. - Wait for both Build and validate distributions and Publish to TestPyPI to finish.
- Verify the new release on TestPyPI. Its version is derived from the declared
version and the unique workflow run ID, for example
0.0.1.dev123456789. - Download the Actions artifact and verify that it contains one wheel, one
source distribution, and
SHA256SUMS.
The version change exists only in the runner workspace. It is never committed and the TestPyPI distributions are never reused for production. If a rehearsal must be repeated after it has published, start a new workflow run instead of rerunning the completed publication job; the new run receives a new version.
- Choose a new, unused
X.Y.Zversion that follows PEP 440. - Create a pull request that changes only
project.versioninpyproject.toml. - Run and approve all required CI checks.
- Merge the version pull request into
main. - Confirm that the version does not already exist on PyPI.
From an up-to-date main, create and push an annotated tag:
git switch main
git pull --ff-only
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.ZDo not create the tag on an unmerged commit. Do not move or reuse it after it has been pushed.
The workflow rejects a production tag unless all of these conditions hold:
- the tag uses the exact
vX.Y.Zformat; - it is annotated;
X.Y.Zmatchesproject.version;- the tagged commit belongs to
origin/main; and - the version does not already exist on PyPI.
-
Approve the
pypienvironment deployment. -
Confirm that Publish to PyPI succeeds.
-
Confirm that Create GitHub Release succeeds afterward.
-
Verify the wheel and source distribution on PyPI.
-
Verify that the GitHub Release contains those same files and
SHA256SUMS. -
Install the exact published version:
pipx run --spec "google-ads-mcp==X.Y.Z" google-ads-mcp -
Close the release issue only after at least one new production version is available and installable.
- Failure before PyPI publication: correct the source through a pull request, choose a new version, and create a new tag. Do not move a pushed release tag.
- PyPI succeeded but GitHub Release failed: use Re-run failed jobs so the release job resumes its draft or verifies the already published assets. Do not rerun the successful PyPI job.
- Defective published version: yank it on PyPI when appropriate and publish a corrected version. PyPI distributions cannot be overwritten.
- Interrupted TestPyPI rehearsal: start a new manual run if the package was already uploaded, because every uploaded version is immutable.
| Symptom | Likely cause | Resolution |
|---|---|---|
| Trusted Publisher reports an invalid publisher | Owner, repository, workflow, or environment differs from the registered identity | Compare all four values character for character and update the index configuration if the workflow was renamed. |
| The workflow cannot request an OIDC token | The publication job lacks id-token: write or is not using the registered environment |
Keep id-token: write on the publication job and select the exact pypi or testpypi environment. |
| Publication waits indefinitely | A protected environment is awaiting review | Ask an allowed reviewer to approve or reject the deployment in GitHub Actions. |
| PyPI says a file or version already exists | The version was already uploaded | Never enable skip-existing for production. Choose a new version and tag. |
| Production validation rejects the tag | The tag is lightweight, mismatches project.version, or is not on main |
Merge the version change first, then create a new annotated tag from the merged commit. |
| A TestPyPI rerun reports a duplicate version | The successful dispatch was rerun with the same run ID | Start a new manual workflow run to generate a new .dev version. |