Files
unraid-mcp/docs/PUBLISHING.md
Jacob Magar efaab031ae fix: address all 17 PR review comments
Resolves review threads:
- PRRT_kwDOO6Hdxs50fewG (setup.py): non-eliciting clients now return True
  from elicit_reset_confirmation so they can reconfigure without being blocked
- PRRT_kwDOO6Hdxs50fewM (test-tools.sh): add notification/recalculate smoke test
- PRRT_kwDOO6Hdxs50fewP (test-tools.sh): add system/array smoke test
- PRRT_kwDOO6Hdxs50fewT (resources.py): surface manager error state instead of
  reporting 'connecting' for permanently failed subscriptions
- PRRT_kwDOO6Hdxs50feAj (resources.py): use is not None check for empty cached dicts
- PRRT_kwDOO6Hdxs50fewY (integration tests): remove duplicate snapshot-registration
  tests already covered in test_resources.py
- PRRT_kwDOO6Hdxs50fewe (test_resources.py): replace brittle import-detail test
  with behavior tests for connecting/error states
- PRRT_kwDOO6Hdxs50fewh (test_customization.py): strengthen public_theme assertion
- PRRT_kwDOO6Hdxs50fewk (test_customization.py): strengthen theme assertion
- PRRT_kwDOO6Hdxs50fewo (__init__.py): correct subaction count ~88 -> ~107
- PRRT_kwDOO6Hdxs50fewx (test_oidc.py): assert providers list value directly
- PRRT_kwDOO6Hdxs50fewz (unraid.py): remove unreachable raise after vm handler
- PRRT_kwDOO6Hdxs50few2 (unraid.py): remove unreachable raise after docker handler
- PRRT_kwDOO6Hdxs50fev8 (CLAUDE.md): replace legacy 15-tool table with unified
  unraid action/subaction table
- PRRT_kwDOO6Hdxs50fev_ (test_oidc.py): assert providers + defaultAllowedOrigins
- PRRT_kwDOO6Hdxs50feAz (CLAUDE.md): update tool categories to unified API shape
- PRRT_kwDOO6Hdxs50feBE (CLAUDE.md/setup.py): update unraid_health refs to
  unraid(action=health, subaction=setup)
2026-03-16 02:58:54 -04:00

6.1 KiB

Publishing Guide for unraid-mcp

This guide covers how to publish unraid-mcp to PyPI so it can be installed via uvx or pip from anywhere.

Prerequisites

  1. PyPI Account: Create accounts on both:

  2. API Tokens: Generate API tokens for automated publishing:

  3. Save Tokens Securely:

    # Add to ~/.pypirc (never commit this file!)
    cat > ~/.pypirc << 'EOF'
    [distutils]
    index-servers =
        pypi
        testpypi
    
    [pypi]
    username = __token__
    password = pypi-YOUR-API-TOKEN-HERE
    
    [testpypi]
    username = __token__
    password = pypi-YOUR-TEST-API-TOKEN-HERE
    repository = https://test.pypi.org/legacy/
    EOF
    
    chmod 600 ~/.pypirc  # Secure the file
    

Version Management

Before publishing, update the version in pyproject.toml:

[project]
version = "1.0.0"  # Follow semantic versioning: MAJOR.MINOR.PATCH

Semantic Versioning Guide:

  • MAJOR (1.0.0): Breaking changes
  • MINOR (0.2.0): New features (backward compatible)
  • PATCH (0.2.1): Bug fixes (backward compatible)

Publishing Workflow

1. Clean Previous Builds

# Remove old build artifacts
rm -rf dist/ build/ *.egg-info/

2. Run Quality Checks

# Lint and format code
uv run ruff check unraid_mcp/
uv run ruff format unraid_mcp/

# Type check
uv run ty check unraid_mcp/

# Run tests
uv run pytest

# Check coverage
uv run pytest --cov=unraid_mcp --cov-report=html

3. Build the Package

# Build both wheel and source distribution
uv run python -m build

This creates:

  • dist/unraid_mcp-VERSION-py3-none-any.whl (wheel)
  • dist/unraid_mcp-VERSION.tar.gz (source distribution)

4. Validate the Package

# Check that the package meets PyPI requirements
uv run twine check dist/*

Expected output: PASSED for both files.

5. Test on Test PyPI (IMPORTANT!)

Always test on Test PyPI first:

# Upload to Test PyPI
uv run twine upload --repository testpypi dist/*

# Test installation from Test PyPI
uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ unraid-mcp-server

# Or with pip in a test environment
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ unraid-mcp

Note: --extra-index-url https://pypi.org/simple/ is needed because dependencies come from production PyPI.

6. Publish to PyPI (Production)

Once testing passes:

# Upload to production PyPI
uv run twine upload dist/*

7. Verify Installation

# Install and run from PyPI using uvx (no installation required!)
uvx unraid-mcp-server --help

# Or install globally
uv tool install unraid-mcp

# Or install in a project
uv add unraid-mcp

Post-Publishing Checklist

  • Create a GitHub Release with the same version tag
  • Update CHANGELOG.md with release notes
  • Test installation on a fresh machine
  • Update documentation if API changed
  • Announce release (if applicable)

Running from Any Machine with uvx

Once published to PyPI, users can run the server without installing:

# Run directly with uvx (recommended)
uvx unraid-mcp-server

# Or with custom environment variables
UNRAID_API_URL=https://your-server uvx unraid-mcp-server

Benefits of uvx:

  • No installation required
  • Automatic virtual environment management
  • Always uses the latest version (or specify version: uvx unraid-mcp-server@1.0.0)
  • Clean execution environment

Automation with GitHub Actions (Future)

PyPI Trusted Publishing uses OpenID Connect (OIDC) to authenticate directly from GitHub Actions -- no stored API tokens or long-lived secrets required. This is PyPI's recommended approach.

Setup:

  1. Go to pypi.org/manage/account/publishing/
  2. Add a "new pending publisher" with your GitHub repository details
  3. Use the following workflow:
name: Publish to PyPI

on:
  release:
    types: [published]

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Required for Trusted Publishing
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - name: Build package
        run: uv run python -m build
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

Alternative: API Token

If Trusted Publishing is not an option, use a stored API token:

name: Publish to PyPI

on:
  release:
    types: [published]

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - name: Build package
        run: uv run python -m build
      - name: Publish to PyPI
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
        run: uv run twine upload dist/*

Troubleshooting

"File already exists" Error

PyPI doesn't allow re-uploading the same version. Options:

  1. Increment version number in pyproject.toml
  2. Delete old dist files and rebuild

Missing Dependencies

If installation fails due to missing dependencies:

  1. Check that all dependencies are in pyproject.toml dependencies section
  2. Ensure dependency version constraints are correct
  3. Test in a clean virtual environment

Import Errors After Installation

If the package installs but imports fail:

  1. Verify package structure in wheel: unzip -l dist/*.whl
  2. Check that __init__.py files exist in all package directories
  3. Ensure packages = ["unraid_mcp"] in [tool.hatch.build.targets.wheel]

Resources