Skip to content

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Jan 26, 2026

Summary

Adds a workflow that publishes beta/dev versions of the Python MCP server to TestPyPI when changes are pushed to the beta branch.

  • Only triggers when there are changes to Server/** (avoids wasted cycles)
  • Generates unique dev versions like 9.2.0.dev202501251430.gabc1234
  • Publishes to TestPyPI, keeping production PyPI clean for stable releases

Admin Setup Required

Before this workflow can run, an admin needs to:

1. Create the GitHub Environment

  1. Go to Settings → Environments → New environment
  2. Name it testpypi
  3. (Optional) Add deployment protection rules if desired

2. Configure Trusted Publishing on TestPyPI

  1. Log in to https://test.pypi.org
  2. Go to Account settings → Publishing → Add a new pending publisher
  3. Fill in:
    • PyPI project name: mcpforunityserver
    • Owner: CoplayDev
    • Repository: unity-mcp
    • Workflow name: beta-release.yml
    • Environment name: testpypi

Usage

Once set up, users can install beta versions with:

pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ mcpforunityserver --pre

🤖 Generated with Claude Code

Summary by Sourcery

Build:

  • Introduce a beta-release workflow that builds the Server package and publishes time- and commit-suffixed dev versions to TestPyPI using Trusted Publishing.

Summary by CodeRabbit

  • Chores
    • Implemented automated beta release pipeline for continuous integration and streamlined distribution of development versions.

✏️ Tip: You can customize this high-level summary in your review settings.

Publishes dev versions to TestPyPI when Server/ changes are pushed to beta.
Only triggers when there are actual changes to avoid wasted cycles.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

Adds a GitHub Actions workflow for automated beta releases to TestPyPI. The workflow triggers on pushes to the beta branch, generates a timestamped development version with git SHA, updates the Server package configuration, builds distributions, and publishes them to TestPyPI.

Changes

Cohort / File(s) Summary
GitHub Actions Beta Release Workflow
.github/workflows/beta-release.yml
New workflow file that automates publishing beta releases to TestPyPI. Includes version generation from base version with dev suffix, dependency installation via uv, distribution building (wheel and tarball), and publication steps.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related PRs

Poem

🐰 A workflow so neat, beta builds complete,
With timestamps and versions, the pipeline's sweet!
To TestPyPI it hops, with uv at full speed,
Fresh dev versions sprout—a beta indeed! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a beta release workflow for TestPyPI, which directly aligns with the changeset.
Description check ✅ Passed The description includes most required template sections (Summary, Type of Change, Changes Made, Related Issues alternatives) but is missing the explicit Type of Change checkbox selection and Testing/Documentation Updates sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 26, 2026

Reviewer's Guide

Adds a GitHub Actions workflow that builds and publishes beta/dev versions of the Server Python package to TestPyPI from the beta branch using uv and trusted publishing, with automatic dev versioning and path-based triggers.

Sequence diagram for TestPyPI beta release workflow execution

sequenceDiagram
  actor Developer
  participant GitHub as GitHub_Repo
  participant Actions as GitHub_Actions
  participant Runner as Actions_Runner
  participant TestPyPI as TestPyPI_Registry

  Developer->>GitHub: push_to_beta_branch(changes_in_Server)
  GitHub-->>Actions: trigger_beta-release_workflow

  Actions->>Runner: start_job_publish_testpypi
  Runner->>Runner: apply_concurrency_group(beta-release)

  Runner->>GitHub: actions_checkout(fetch_depth_0)
  GitHub-->>Runner: repository_with_history

  Runner->>Runner: setup_uv(enable_cache, uv_lock_Server)

  Runner->>Runner: read_base_version_from_pyproject
  Runner->>Runner: compute_dev_suffix(date_time, git_short_sha)
  Runner->>Runner: set_dev_version_output

  Runner->>Runner: update_pyproject_version(dev_version)

  Runner->>Runner: uv_build_in_Server_directory

  Runner->>Actions: request_oidc_id_token_for_testpypi_environment
  Actions-->>Runner: oidc_id_token

  Runner->>TestPyPI: publish_distributions_with_trusted_publishing
  TestPyPI-->>Runner: publish_success_response
  Runner-->>Actions: job_completed_successfully
Loading

File-Level Changes

Change Details Files
Introduce a new GitHub Actions workflow to build and publish beta releases to TestPyPI from the beta branch.
  • Define a Beta Release (TestPyPI) workflow that runs on pushes to the beta branch limited to changes under Server/** and uses a concurrency group beta-release with in-progress runs cancelled.
  • Configure a publish_testpypi job targeting the testpypi environment with appropriate permissions for GitHub OIDC trusted publishing and a TestPyPI project URL.
  • Check out the repository with full history (fetch-depth: 0) and install uv via astral-sh/setup-uv@v7 with caching based on Server/uv.lock.
  • Add a bash step that derives a base version from Server/pyproject.toml, constructs a unique dev version using timestamp and short git SHA, and exposes it as a workflow output.
  • Add a bash step that in-place updates the version field in Server/pyproject.toml to the generated dev version, echoing the change for visibility in logs.
  • Build the Server Python package (wheel and sdist) using uv build from the Server directory and publish the artifacts to TestPyPI using pypa/gh-action-pypi-publish@release/v1 with the TestPyPI legacy repository URL and Server/dist/ as the packages directory.
.github/workflows/beta-release.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The generated dev version string (${BASE_VERSION}.dev<timestamp>.g<sha>) likely violates PEP 440 (the .devN segment must end with a purely numeric identifier), so consider either making the dev suffix purely numeric or moving the git hash into a local version segment using +g<sha> (e.g. 9.2.0.dev202501251430+gabc1234).
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The generated dev version string (`${BASE_VERSION}.dev<timestamp>.g<sha>`) likely violates PEP 440 (the `.devN` segment must end with a purely numeric identifier), so consider either making the dev suffix purely numeric or moving the git hash into a local version segment using `+g<sha>` (e.g. `9.2.0.dev202501251430+gabc1234`).

## Individual Comments

### Comment 1
<location> `.github/workflows/beta-release.yml:43-44` </location>
<code_context>
+          set -euo pipefail
+          BASE_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml)
+          # Use date + short SHA for unique dev version
+          DEV_SUFFIX="dev$(date +%Y%m%d%H%M).g$(git rev-parse --short HEAD)"
+          DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}"
+          echo "Base version: $BASE_VERSION"
+          echo "Dev version: $DEV_VERSION"
</code_context>

<issue_to_address>
**issue (bug_risk):** The generated dev version format may not be strictly PEP 440 compliant and could still collide on rapid successive runs.

The format `${BASE_VERSION}.devYYYYMMDDHHMM.g<sha>` has two problems:

1) **PEP 440**: Only `.devN` is standard. Appending `.g<sha>` after the dev segment is non‑standard and may be rejected by some tooling or future PyPI checks.
2) **Collisions**: `YYYYMMDDHHMM` has minute granularity, so two builds of the same base version within a minute will generate the same version and be rejected as duplicates.

To address both, consider something like:

```bash
timestamp=$(date +%Y%m%d%H%M%S)
DEV_VERSION="${BASE_VERSION}.dev${timestamp}+g$(git rev-parse --short HEAD)"
```

This keeps the public version PEP 440–compliant (`X.Y.Z.devN`) and moves the SHA into a local version, while reducing collision risk by including seconds.
</issue_to_address>

### Comment 2
<location> `.github/workflows/beta-release.yml:41-50` </location>
<code_context>
+        shell: bash
+        run: |
+          set -euo pipefail
+          BASE_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml)
+          # Use date + short SHA for unique dev version
+          DEV_SUFFIX="dev$(date +%Y%m%d%H%M).g$(git rev-parse --short HEAD)"
+          DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}"
+          echo "Base version: $BASE_VERSION"
+          echo "Dev version: $DEV_VERSION"
+          echo "dev_version=$DEV_VERSION" >> "$GITHUB_OUTPUT"
+
+      - name: Update version for beta release
+        env:
+          DEV_VERSION: ${{ steps.version.outputs.dev_version }}
+        shell: bash
+        run: |
+          set -euo pipefail
+          sed -i "s/^version = .*/version = \"${DEV_VERSION}\"/" Server/pyproject.toml
+          echo "Updated pyproject.toml:"
+          grep "^version" Server/pyproject.toml
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Version extraction and replacement are a bit brittle if pyproject.toml layout changes or contains multiple `version` keys.

This approach assumes there is a single `version = "..."` line at the start of a line and that no other tables (e.g. tool configs) define a `version` key. If additional sections are added later, it could read or overwrite the wrong field. Consider either parsing `pyproject.toml` via `python -m tomllib`/`tomli` and updating `[project].version` explicitly, or at least constraining the `grep`/`sed` to the `[project]` section (e.g. with an `awk`/`sed` range) to avoid touching other `version` keys.

Suggested implementation:

```
      - name: Generate dev version
        id: version
        shell: bash
        run: |
          set -euo pipefail
          BASE_VERSION=$(
            python - << 'PY'
import tomllib
from pathlib import Path

data = tomllib.loads(Path("Server/pyproject.toml").read_text(encoding="utf-8"))
print(data["project"]["version"])
PY
          )
          # Use date + short SHA for unique dev version
          DEV_SUFFIX="dev$(date +%Y%m%d%H%M).g$(git rev-parse --short HEAD)"
          DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}"
          echo "Base version: $BASE_VERSION"
          echo "Dev version: $DEV_VERSION"
          echo "dev_version=$DEV_VERSION" >> "$GITHUB_OUTPUT"

```

```
      - name: Update version for beta release
        env:
          DEV_VERSION: ${{ steps.version.outputs.dev_version }}
        shell: bash
        run: |
          set -euo pipefail
          awk -v new_version="$DEV_VERSION" '
            /^\[project]/ { in_project = 1 }
            /^\[/ && $0 !~ /^\[project]/ && in_project { in_project = 0 }
            in_project && /^version = / {
              sub(/=.*/, "= \"" new_version "\"")
            }
            { print }
          ' Server/pyproject.toml > Server/pyproject.toml.tmp
          mv Server/pyproject.toml.tmp Server/pyproject.toml
          echo "Updated pyproject.toml [project] version:"
          awk '
            BEGIN { in_project = 0 }
            /^\[project]/ { in_project = 1 }
            /^\[/ && $0 !~ /^\[project]/ && in_project { in_project = 0 }
            in_project && /^version = / { print }
          ' Server/pyproject.toml

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +43 to +44
DEV_SUFFIX="dev$(date +%Y%m%d%H%M).g$(git rev-parse --short HEAD)"
DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The generated dev version format may not be strictly PEP 440 compliant and could still collide on rapid successive runs.

The format ${BASE_VERSION}.devYYYYMMDDHHMM.g<sha> has two problems:

  1. PEP 440: Only .devN is standard. Appending .g<sha> after the dev segment is non‑standard and may be rejected by some tooling or future PyPI checks.
  2. Collisions: YYYYMMDDHHMM has minute granularity, so two builds of the same base version within a minute will generate the same version and be rejected as duplicates.

To address both, consider something like:

timestamp=$(date +%Y%m%d%H%M%S)
DEV_VERSION="${BASE_VERSION}.dev${timestamp}+g$(git rev-parse --short HEAD)"

This keeps the public version PEP 440–compliant (X.Y.Z.devN) and moves the SHA into a local version, while reducing collision risk by including seconds.

Comment on lines +41 to +50
BASE_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml)
# Use date + short SHA for unique dev version
DEV_SUFFIX="dev$(date +%Y%m%d%H%M).g$(git rev-parse --short HEAD)"
DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}"
echo "Base version: $BASE_VERSION"
echo "Dev version: $DEV_VERSION"
echo "dev_version=$DEV_VERSION" >> "$GITHUB_OUTPUT"

- name: Update version for beta release
env:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Version extraction and replacement are a bit brittle if pyproject.toml layout changes or contains multiple version keys.

This approach assumes there is a single version = "..." line at the start of a line and that no other tables (e.g. tool configs) define a version key. If additional sections are added later, it could read or overwrite the wrong field. Consider either parsing pyproject.toml via python -m tomllib/tomli and updating [project].version explicitly, or at least constraining the grep/sed to the [project] section (e.g. with an awk/sed range) to avoid touching other version keys.

Suggested implementation:

      - name: Generate dev version
        id: version
        shell: bash
        run: |
          set -euo pipefail
          BASE_VERSION=$(
            python - << 'PY'
import tomllib
from pathlib import Path

data = tomllib.loads(Path("Server/pyproject.toml").read_text(encoding="utf-8"))
print(data["project"]["version"])
PY
          )
          # Use date + short SHA for unique dev version
          DEV_SUFFIX="dev$(date +%Y%m%d%H%M).g$(git rev-parse --short HEAD)"
          DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}"
          echo "Base version: $BASE_VERSION"
          echo "Dev version: $DEV_VERSION"
          echo "dev_version=$DEV_VERSION" >> "$GITHUB_OUTPUT"

      - name: Update version for beta release
        env:
          DEV_VERSION: ${{ steps.version.outputs.dev_version }}
        shell: bash
        run: |
          set -euo pipefail
          awk -v new_version="$DEV_VERSION" '
            /^\[project]/ { in_project = 1 }
            /^\[/ && $0 !~ /^\[project]/ && in_project { in_project = 0 }
            in_project && /^version = / {
              sub(/=.*/, "= \"" new_version "\"")
            }
            { print }
          ' Server/pyproject.toml > Server/pyproject.toml.tmp
          mv Server/pyproject.toml.tmp Server/pyproject.toml
          echo "Updated pyproject.toml [project] version:"
          awk '
            BEGIN { in_project = 0 }
            /^\[project]/ { in_project = 1 }
            /^\[/ && $0 !~ /^\[project]/ && in_project { in_project = 0 }
            in_project && /^version = / { print }
          ' Server/pyproject.toml

@dsarno
Copy link
Collaborator Author

dsarno commented Jan 26, 2026

@msanatan I can't test this because I can't set up the testpypi environment etc. Happy to do it if you give me the right admin permissions, or you can try to give it a crack when you have time. Until then I think beta users will be getting the main version of the python server (still pointing to regular PyPi) -- which might break the loop for beta users now.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.github/workflows/beta-release.yml:
- Around line 41-44: The dev version currently built in DEV_SUFFIX/DEV_VERSION
creates an extra dot segment after .dev and thus violates PEP 440; update the
construction so the timestamp remains the .dev numeric segment and move the git
SHA into a local version after a + sign. Specifically, change the logic around
BASE_VERSION, DEV_SUFFIX and DEV_VERSION to produce something like BASE_VERSION
+ ".dev<YYYYMMDDHHMM>+g<sha>" (compute timestamp and short git SHA as before) so
the version is PEP 440–compliant.
🧹 Nitpick comments (1)
.github/workflows/beta-release.yml (1)

29-33: Consider pinning uv version for reproducibility.

The astral-sh/setup-uv@v7 action tag is valid and currently points to v7.2.0. However, using version: "latest" combined with a moving major version tag means the workflow can change in two ways. For better reproducibility, pin the uv version to a specific release (e.g., version: "0.5.7").

Comment on lines +41 to +44
BASE_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml)
# Use date + short SHA for unique dev version
DEV_SUFFIX="dev$(date +%Y%m%d%H%M).g$(git rev-parse --short HEAD)"
DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

PEP 440 development release local version identifier format specification

💡 Result:

Development release segment (PEP 440)

  • A development release is indicated by a .devN suffix, where N is a non-negative integer:
    ... .devN (e.g., 1.2.dev3). (packaging.python.org)
  • If the numeral is omitted, it is implicitly 0 (so 1.2.dev normalizes to 1.2.dev0). (peps.python.org)

Local version identifier (PEP 440)

  • A local version identifier is a public version followed by + and a local version label:
    <public version>[+<local version label>] (e.g., 1.0+ubuntu.1). (peps.python.org)
  • Local version labels:
    • May contain only ASCII letters, digits, and . (peps.python.org)
    • Must start and end with a letter or digit (peps.python.org)
    • Are split into segments by . for comparison; segments that are all digits compare numerically, otherwise lexicographically (case-insensitive). (peps.python.org)
    • May use - or _ as separators in input, but these are normalized to . (e.g., 1.0+ubuntu-11.0+ubuntu.1). (peps.python.org)

Combined example

  • A development release with a local label is valid, e.g.:
    1.2.dev3+local.1 (peps.python.org)

Citations:


Ensure dev version string is PEP 440–compliant.

The current format BASE_VERSION.devYYYYMMDDHHMM.g<sha> violates PEP 440. After the .dev segment, only a numeric identifier is allowed; the .g<sha> portion creates an invalid additional segment. Use a local version identifier with + to separate the SHA:

Proposed fix (PEP 440)
-          DEV_SUFFIX="dev$(date +%Y%m%d%H%M).g$(git rev-parse --short HEAD)"
-          DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}"
+          DEV_SUFFIX="dev$(date -u +%Y%m%d%H%M)"
+          DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}+g$(git rev-parse --short HEAD)"

This will be rejected by TestPyPI and other package registries as non-compliant.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
BASE_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml)
# Use date + short SHA for unique dev version
DEV_SUFFIX="dev$(date +%Y%m%d%H%M).g$(git rev-parse --short HEAD)"
DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}"
BASE_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml)
# Use date + short SHA for unique dev version
DEV_SUFFIX="dev$(date -u +%Y%m%d%H%M)"
DEV_VERSION="${BASE_VERSION}.${DEV_SUFFIX}+g$(git rev-parse --short HEAD)"
🤖 Prompt for AI Agents
In @.github/workflows/beta-release.yml around lines 41 - 44, The dev version
currently built in DEV_SUFFIX/DEV_VERSION creates an extra dot segment after
.dev and thus violates PEP 440; update the construction so the timestamp remains
the .dev numeric segment and move the git SHA into a local version after a +
sign. Specifically, change the logic around BASE_VERSION, DEV_SUFFIX and
DEV_VERSION to produce something like BASE_VERSION + ".dev<YYYYMMDDHHMM>+g<sha>"
(compute timestamp and short git SHA as before) so the version is PEP
440–compliant.

@msanatan msanatan merged commit 74fab4b into CoplayDev:beta Jan 27, 2026
2 checks passed
@msanatan
Copy link
Member

@dsarno good stuff, I just set it up to test if it works (it should) and I'll add you to the testpypi project

@dsarno dsarno deleted the feature/beta-testpypi-release branch January 29, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants