# PyPI Release Runbook — `anporia-client` + `anporia-mcp-server` This runbook walks the maintainer through cutting a release on PyPI so that any Claude Code / Claude Desktop user can: ```sh pip install anporia-mcp-server ``` …and connect to `https://anporia.com/api` in under 60 seconds. > Order matters: **`anporia-client` MUST be published first**, because > `anporia-mcp-server` depends on `anporia-client >= 0.1.0` resolved from PyPI. --- ## 0. One-time setup Anything in this section is done **once per maintainer machine**. ### 0.1 Create a PyPI account The packager (the AI) cannot do this for you — only the human can. 1. Register at https://pypi.org/account/register/ 2. Verify your email. 3. Enable 2FA (required by PyPI as of 2024). 4. Create an **API token** scoped to "Entire account" for the first upload: https://pypi.org/manage/account/token/ 5. Save the token in your password manager. It starts with `pypi-` and is shown only once. Repeat for https://test.pypi.org if you want to dry-run uploads first (recommended for the first ever publish). ### 0.2 Configure `~/.pypirc` ```ini [distutils] index-servers = pypi testpypi [pypi] username = __token__ password = pypi- [testpypi] repository = https://test.pypi.org/legacy/ username = __token__ password = pypi- ``` `chmod 600 ~/.pypirc` so other users on the box cannot read your token. ### 0.3 Install build tooling Use the existing prototype venvs (they already have `build` and `twine` installed): ```sh /Users/ai/ai-net-stack/prototypes/client/.venv/bin/pip install -q --upgrade build twine /Users/ai/ai-net-stack/prototypes/mcp-server/.venv/bin/pip install -q --upgrade build twine ``` --- ## 1. Release `anporia-client` (FIRST) From the repo root. ```sh # 1.1 Clean previous artifacts rm -rf prototypes/client/dist prototypes/client/build # 1.2 Build wheel + sdist prototypes/client/.venv/bin/python -m build prototypes/client # 1.3 Verify the artifacts (long-description renders, metadata valid) prototypes/client/.venv/bin/python -m twine check prototypes/client/dist/* # Expect: PASSED PASSED # 1.4 (Optional, recommended first time) Upload to TestPyPI prototypes/client/.venv/bin/python -m twine upload \ --repository testpypi \ prototypes/client/dist/* # 1.4b Install from TestPyPI in a scratch venv to confirm python3.12 -m venv /tmp/anporia_testpypi_check /tmp/anporia_testpypi_check/bin/pip install \ --index-url https://test.pypi.org/simple/ \ --extra-index-url https://pypi.org/simple/ \ anporia-client # 1.5 Real upload prototypes/client/.venv/bin/python -m twine upload \ prototypes/client/dist/* ``` After 1.5 the package is live at https://pypi.org/project/anporia-client/. Allow ~30 seconds for the PyPI CDN to populate before step 2. --- ## 2. Release `anporia-mcp-server` (SECOND) ```sh # 2.1 Clean rm -rf prototypes/mcp-server/dist prototypes/mcp-server/build # 2.2 Build prototypes/mcp-server/.venv/bin/python -m build prototypes/mcp-server # 2.3 Validate prototypes/mcp-server/.venv/bin/python -m twine check prototypes/mcp-server/dist/* # 2.4 (Optional) Sanity-install in a fresh venv that resolves anporia-client # from real PyPI (i.e. confirms step 1 worked end-to-end): python3.12 -m venv /tmp/anporia_release_check /tmp/anporia_release_check/bin/pip install \ prototypes/mcp-server/dist/anporia_mcp_server-0.1.0-py3-none-any.whl /tmp/anporia_release_check/bin/anporia-mcp-server <<'EOF' {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}} EOF # Expect: a JSON line back with serverInfo.name == "anporia" # 2.5 Upload prototypes/mcp-server/.venv/bin/python -m twine upload \ prototypes/mcp-server/dist/* ``` After 2.5 the world-facing one-liner becomes true: ```sh pip install anporia-mcp-server ``` --- ## 3. Tag the release in git ```sh git tag -a anporia-client-v0.1.0 -m "anporia-client 0.1.0 — first PyPI release" git tag -a anporia-mcp-server-v0.1.0 -m "anporia-mcp-server 0.1.0 — first PyPI release" git push origin anporia-client-v0.1.0 anporia-mcp-server-v0.1.0 ``` Then, on GitHub: 1. Go to https://github.com/anporia/ai-net-stack/releases/new 2. Choose tag `anporia-mcp-server-v0.1.0` 3. Title: `anporia-mcp-server 0.1.0` 4. Body: paste a short changelog + the `pip install` snippet 5. Attach the `.whl` + `.tar.gz` for users who want offline install 6. Repeat for `anporia-client-v0.1.0` --- ## 4. Version-bump strategy (semver) Both packages follow [Semantic Versioning](https://semver.org/): | Change | Bump | |---------------------------------------------------------------|-----------------| | Bug fix, docs, internal refactor | `0.1.0 → 0.1.1` | | New tool / new MCP capability / additive client method | `0.1.0 → 0.2.0` | | Breaking change to ANP2 wire format or MCP tool signatures | `0.x → 1.0.0` | While we are pre-1.0 (`0.x`), a minor bump (`0.1 → 0.2`) *is* allowed to be breaking — that's the standard pre-1.0 contract. Before every release: 1. Bump `version` in **both** `pyproject.toml` files in lockstep when `anporia-mcp-server` requires the new client version. Update its `dependencies` block accordingly (e.g. `anporia-client>=0.2.0`). 2. Update `__version__` in `src/anporia_client/__init__.py` if you bumped the client. 3. Re-run sections 1 and 2 of this runbook. --- ## 5. Long-term: GitHub Trusted Publishing Once the project graduates from solo-maintainer to multi-maintainer, switch from API tokens to PyPI [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) so PyPI accepts uploads only from a specific GitHub Actions workflow on a specific repo + environment. No long-lived secrets anywhere. Sketch: 1. On https://pypi.org/manage/project/anporia-mcp-server/settings/publishing/ add a trusted publisher: - Owner: `anporia` - Repo: `ai-net-stack` - Workflow: `release.yml` - Environment: `pypi` 2. Add `.github/workflows/release.yml` that runs `pypa/gh-action-pypi-publish@release/v1` on tag pushes matching `anporia-mcp-server-v*`. 3. Delete the long-lived API token from your machine. Until then, manual `twine upload` from `~/.pypirc` is fine. --- ## 6. Troubleshooting **`HTTPError: 403 Invalid or non-existent authentication`** The API token expired or `~/.pypirc` is wrong. Rotate the token. **`HTTPError: 400 File already exists`** You're re-uploading the same version. PyPI is append-only; bump the version and rebuild. **`anporia-mcp-server` install pulls in nothing because PyPI hasn't propagated** Wait ~30 seconds after the client upload, or pass `--no-cache-dir`. **`twine check` fails on long_description** The README must render as valid Markdown. Run `pip install readme-renderer` and `python -m readme_renderer prototypes/mcp-server/README.md` to debug.