# 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:
- [Test PyPI](https://test.pypi.org/account/register/) (for testing)
- [PyPI](https://pypi.org/account/register/) (for production)
2. **API Tokens**: Generate API tokens for automated publishing:
- Test PyPI: https://test.pypi.org/manage/account/token/
- PyPI: https://pypi.org/manage/account/token/
3. **Save Tokens Securely**:
```bash
# 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`:
```toml
[project]
version = "0.2.1" # 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
```bash
# Remove old build artifacts
rm -rf dist/ build/ *.egg-info/
```
### 2. Run Quality Checks
```bash
# 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
```bash
# 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
```bash
# 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:
```bash
# 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:
```bash
# Upload to production PyPI
uv run twine upload dist/*
```
### 7. Verify Installation
```bash
# 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:
```bash
# 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@0.2.0`)
- Clean execution environment
## Automation with GitHub Actions (Future)
### Recommended: Trusted Publishing (OIDC)
[PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) 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/](https://pypi.org/manage/account/publishing/)
2. Add a "new pending publisher" with your GitHub repository details
3. Use the following workflow:
```yaml
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:
```yaml
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
- [PyPI Publishing Guide](https://packaging.python.org/tutorials/packaging-projects/)
- [Semantic Versioning](https://semver.org/)
- [Python Packaging User Guide](https://packaging.python.org/)
- [uv Documentation](https://docs.astral.sh/uv/)
- [Twine Documentation](https://twine.readthedocs.io/)