Auto Release workflow (stickerdaniel/linkedin-mcp-server)
The Auto Release workflow from stickerdaniel/linkedin-mcp-server, explained and optimized by Latchkey.
CI health: C - fair
Point runs-on at Latchkey and get run de-duplication, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Auto Release workflow from the stickerdaniel/linkedin-mcp-server repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
# .github/workflows/release.yml
name: Auto Release
on:
push:
branches: [main]
paths: ['pyproject.toml'] # Only trigger when pyproject.toml changes
jobs:
check-version-bump:
runs-on: ubuntu-latest
outputs:
should-release: ${{ steps.check.outputs.should-release }}
new-version: ${{ steps.check.outputs.new-version }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 2 # Need to compare with previous commit
- name: Set up uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
enable-cache: true
- name: Check if version was bumped
id: check
run: |
# Get current version
CURRENT_VERSION=$(uv version | cut -d' ' -f2)
echo "Current version: $CURRENT_VERSION"
# Get previous version from git (before this commit)
git checkout HEAD~1 -- pyproject.toml || true
PREVIOUS_VERSION=$(uv version | cut -d' ' -f2) 2>/dev/null || echo "0.0.0"
git checkout HEAD -- pyproject.toml
echo "Previous version: $PREVIOUS_VERSION"
# Check if version actually changed
if [[ "$CURRENT_VERSION" != "$PREVIOUS_VERSION" ]]; then
echo "✅ Version bump detected: $PREVIOUS_VERSION → $CURRENT_VERSION"
echo "should-release=true" >> $GITHUB_OUTPUT
echo "new-version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
else
echo "ℹ️ No version change detected"
echo "should-release=false" >> $GITHUB_OUTPUT
fi
release:
needs: check-version-bump
if: needs.check-version-bump.outputs.should-release == 'true'
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.check-version-bump.outputs.new-version }}
permissions:
contents: write
packages: write
id-token: write # Required for PyPI Trusted Publishing
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
- name: Set up uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
enable-cache: true
# PyPI is the cutover-critical artifact: publish first so a failure in
# any later step (Docker build, MCPB pack) cannot leave the README
# pointing at a package version that does not exist yet.
- name: Build package distributions
run: |
uv build
echo "Built package distributions:"
ls -lh dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
print-hash: true
verbose: true
skip-existing: true
- name: Set up Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Update manifest.json and docker-compose.yml version
run: |
set -e
sed -i 's/"version": ".*"/"version": "'$VERSION'"/' manifest.json
sed -i 's/stickerdaniel\/linkedin-mcp-server:[^ ]*/stickerdaniel\/linkedin-mcp-server:'$VERSION'/' docker-compose.yml
echo "✅ Updated manifest.json and docker-compose.yml to version $VERSION"
- name: Remove branch protection (temporary)
run: |
gh api repos/${{ github.repository }}/branches/main/protection \
--method DELETE
env:
GH_TOKEN: ${{ secrets.GH_ADMIN_TOKEN }}
- name: Commit version updates
run: |
set -e
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add manifest.json docker-compose.yml
if git diff --staged --quiet; then
echo "ℹ️ No changes to commit"
else
git commit -m "chore: update manifest.json and docker-compose.yml to v$VERSION [skip ci]"
git push origin main
echo "✅ Committed version updates"
fi
- name: Restore branch protection
if: always()
env:
GH_TOKEN: ${{ secrets.GH_ADMIN_TOKEN }}
PAYLOAD: >-
{
"required_status_checks": {
"strict": true,
"checks": [
{"context": "lint-and-check", "app_id": 15368},
{"context": "test", "app_id": 15368}
]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"dismiss_stale_reviews": false,
"require_code_owner_reviews": false,
"required_approving_review_count": 0
},
"restrictions": null
}
run: |
echo "$PAYLOAD" | gh api repos/${{ github.repository }}/branches/main/protection \
--method PUT \
--input -
- name: Create release tag
run: |
set -e
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
if git tag -l "v$VERSION" | grep -q "v$VERSION"; then
echo "⚠️ Tag v$VERSION already exists, skipping tag creation"
else
git tag "v$VERSION"
git push origin "v$VERSION"
echo "✅ Created and pushed tag v$VERSION"
fi
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Log in to Docker Hub
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker images
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
with:
context: .
push: true
tags: |
stickerdaniel/linkedin-mcp-server:${{ env.VERSION }}
stickerdaniel/linkedin-mcp-server:latest
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Update Docker Hub description
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: stickerdaniel/linkedin-mcp-server
readme-filepath: docs/docker-hub.md
- name: Optimize uv cache for CI
run: uv cache prune --ci
- name: Validate and build MCP bundle
run: |
bunx @anthropic-ai/mcpb validate manifest.json
bunx @anthropic-ai/mcpb pack .
mv linkedin-mcp-server.mcpb linkedin-mcp-server-v$VERSION.mcpb
- name: Generate release notes
run: |
envsubst < RELEASE_NOTES_TEMPLATE.md > RELEASE_NOTES.md
echo "✅ Generated release notes from template"
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3
with:
tag_name: v${{ env.VERSION }}
files: |
*.mcpb
generate_release_notes: true
draft: false
prerelease: false
name: "LinkedIn MCP Server v${{ env.VERSION }}"
body_path: RELEASE_NOTES.md
- name: Summary
run: |
echo "Successfully released v$VERSION!"
echo "Docker: stickerdaniel/linkedin-mcp-server:$VERSION"
echo "PyPI: https://pypi.org/project/mcp-server-linkedin/$VERSION/"
echo "GitHub: https://github.com/${{ github.repository }}/releases/tag/v$VERSION"
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
# .github/workflows/release.yml name: Auto Release on: push: branches: [main] paths: ['pyproject.toml'] # Only trigger when pyproject.toml changes concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: check-version-bump: timeout-minutes: 30 runs-on: latchkey-small outputs: should-release: ${{ steps.check.outputs.should-release }} new-version: ${{ steps.check.outputs.new-version }} steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 2 # Need to compare with previous commit - name: Set up uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: enable-cache: true - name: Check if version was bumped id: check run: | # Get current version CURRENT_VERSION=$(uv version | cut -d' ' -f2) echo "Current version: $CURRENT_VERSION" # Get previous version from git (before this commit) git checkout HEAD~1 -- pyproject.toml || true PREVIOUS_VERSION=$(uv version | cut -d' ' -f2) 2>/dev/null || echo "0.0.0" git checkout HEAD -- pyproject.toml echo "Previous version: $PREVIOUS_VERSION" # Check if version actually changed if [[ "$CURRENT_VERSION" != "$PREVIOUS_VERSION" ]]; then echo "✅ Version bump detected: $PREVIOUS_VERSION → $CURRENT_VERSION" echo "should-release=true" >> $GITHUB_OUTPUT echo "new-version=$CURRENT_VERSION" >> $GITHUB_OUTPUT else echo "ℹ️ No version change detected" echo "should-release=false" >> $GITHUB_OUTPUT fi release: timeout-minutes: 30 needs: check-version-bump if: needs.check-version-bump.outputs.should-release == 'true' runs-on: latchkey-small env: VERSION: ${{ needs.check-version-bump.outputs.new-version }} permissions: contents: write packages: write id-token: write # Required for PyPI Trusted Publishing steps: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 0 - name: Set up uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: enable-cache: true # PyPI is the cutover-critical artifact: publish first so a failure in # any later step (Docker build, MCPB pack) cannot leave the README # pointing at a package version that does not exist yet. - name: Build package distributions run: | uv build echo "Built package distributions:" ls -lh dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 with: print-hash: true verbose: true skip-existing: true - name: Set up Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - name: Update manifest.json and docker-compose.yml version run: | set -e sed -i 's/"version": ".*"/"version": "'$VERSION'"/' manifest.json sed -i 's/stickerdaniel\/linkedin-mcp-server:[^ ]*/stickerdaniel\/linkedin-mcp-server:'$VERSION'/' docker-compose.yml echo "✅ Updated manifest.json and docker-compose.yml to version $VERSION" - name: Remove branch protection (temporary) run: | gh api repos/${{ github.repository }}/branches/main/protection \ --method DELETE env: GH_TOKEN: ${{ secrets.GH_ADMIN_TOKEN }} - name: Commit version updates run: | set -e git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" git add manifest.json docker-compose.yml if git diff --staged --quiet; then echo "ℹ️ No changes to commit" else git commit -m "chore: update manifest.json and docker-compose.yml to v$VERSION [skip ci]" git push origin main echo "✅ Committed version updates" fi - name: Restore branch protection if: always() env: GH_TOKEN: ${{ secrets.GH_ADMIN_TOKEN }} PAYLOAD: >- { "required_status_checks": { "strict": true, "checks": [ {"context": "lint-and-check", "app_id": 15368}, {"context": "test", "app_id": 15368} ] }, "enforce_admins": true, "required_pull_request_reviews": { "dismiss_stale_reviews": false, "require_code_owner_reviews": false, "required_approving_review_count": 0 }, "restrictions": null } run: | echo "$PAYLOAD" | gh api repos/${{ github.repository }}/branches/main/protection \ --method PUT \ --input - - name: Create release tag run: | set -e git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" if git tag -l "v$VERSION" | grep -q "v$VERSION"; then echo "⚠️ Tag v$VERSION already exists, skipping tag creation" else git tag "v$VERSION" git push origin "v$VERSION" echo "✅ Created and pushed tag v$VERSION" fi - name: Set up Docker Buildx uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 - name: Log in to Docker Hub uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker images uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7 with: context: . push: true tags: | stickerdaniel/linkedin-mcp-server:${{ env.VERSION }} stickerdaniel/linkedin-mcp-server:latest platforms: linux/amd64,linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max - name: Update Docker Hub description uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: stickerdaniel/linkedin-mcp-server readme-filepath: docs/docker-hub.md - name: Optimize uv cache for CI run: uv cache prune --ci - name: Validate and build MCP bundle run: | bunx @anthropic-ai/mcpb validate manifest.json bunx @anthropic-ai/mcpb pack . mv linkedin-mcp-server.mcpb linkedin-mcp-server-v$VERSION.mcpb - name: Generate release notes run: | envsubst < RELEASE_NOTES_TEMPLATE.md > RELEASE_NOTES.md echo "✅ Generated release notes from template" - name: Create GitHub Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3 with: tag_name: v${{ env.VERSION }} files: | *.mcpb generate_release_notes: true draft: false prerelease: false name: "LinkedIn MCP Server v${{ env.VERSION }}" body_path: RELEASE_NOTES.md - name: Summary run: | echo "Successfully released v$VERSION!" echo "Docker: stickerdaniel/linkedin-mcp-server:$VERSION" echo "PyPI: https://pypi.org/project/mcp-server-linkedin/$VERSION/" echo "GitHub: https://github.com/${{ github.repository }}/releases/tag/v$VERSION"
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Container pulls and builds
This workflow runs 2 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.