main.yml•10.6 kB
name: Build and Publish CodeAlive MCP Server
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
DOCKER_REGISTRY: ghcr.io
DOCKER_USERNAME: ${{ github.actor }}
DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
IMAGE_NAME: ghcr.io/codealive-ai/codealive-mcp
permissions:
id-token: write # Required for MCP Registry OIDC authentication
contents: write # For creating tags and releases
packages: write
jobs:
build-and-publish:
name: Build, Test, and Publish MCP Server
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for version detection
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pytest pytest-asyncio pytest-mock pytest-cov jsonschema
- name: Run tests
run: |
python -m pytest src/tests/ -v --cov=src --cov-report=term-missing --cov-report=xml --junitxml=junit/test-results.xml
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: pytest-results
path: |
junit/test-results.xml
coverage.xml
- name: Check for version change
id: version-check
run: |
# Get current version from pyproject.toml
CURRENT_VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
# Check if tag already exists
if git tag -l "v$CURRENT_VERSION" | grep -q "v$CURRENT_VERSION"; then
echo "version_changed=false" >> $GITHUB_OUTPUT
echo "Tag v$CURRENT_VERSION already exists"
else
echo "version_changed=true" >> $GITHUB_OUTPUT
echo "New version detected: $CURRENT_VERSION"
fi
- name: Update server.json version
if: steps.version-check.outputs.version_changed == 'true'
run: |
python -c "
import json
with open('server.json', 'r') as f:
data = json.load(f)
version = '${{ steps.version-check.outputs.current_version }}'
# Update top-level version
data['version'] = version
# Update all packages
if 'packages' in data:
for package in data['packages']:
registry_type = package.get('registryType')
# Update version field (required for all packages in 2025-09-29)
package['version'] = version
# Update identifier with new version tag
if registry_type == 'oci':
identifier = package.get('identifier', '')
if ':' in identifier:
base = identifier.rsplit(':', 1)[0]
package['identifier'] = f'{base}:{version}'
with open('server.json', 'w') as f:
json.dump(data, f, indent=2)
"
- name: Validate server.json
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
run: |
python - <<'PY'
from textwrap import dedent
exec(
dedent(
"""
import json
import sys
import urllib.request
from jsonschema import ValidationError, validate
schema_url = "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json"
try:
with urllib.request.urlopen(schema_url, timeout=30) as response:
schema = json.load(response)
except Exception as exc:
print(f"✗ Unable to download server.json schema: {exc}")
sys.exit(1)
try:
with open('server.json', 'r') as f:
data = json.load(f)
except Exception as exc:
print(f"✗ Failed to load server.json: {exc}")
sys.exit(1)
try:
validate(instance=data, schema=schema)
print('✓ server.json schema validation passed')
except ValidationError as exc:
print(f"✗ server.json schema validation failed: {exc.message}")
sys.exit(1)
has_packages = 'packages' in data and len(data['packages']) > 0
has_remotes = 'remotes' in data and len(data['remotes']) > 0
if not (has_packages or has_remotes):
print('✗ Must have either packages or remotes configured')
sys.exit(1)
if has_packages:
for idx, pkg in enumerate(data['packages']):
registry_type = pkg.get('registryType')
identifier = pkg.get('identifier')
if registry_type not in ['npm', 'pypi', 'nuget', 'oci', 'mcpb']:
print(f"✗ Package {idx}: Invalid registry type '{registry_type}'")
sys.exit(1)
print(f"✓ Package {idx}: {registry_type.upper()} -> {identifier}")
if has_remotes:
for idx, remote in enumerate(data['remotes']):
remote_type = remote.get('type')
url = remote.get('url')
if remote_type not in ['sse', 'streamable-http']:
print(f"✗ Remote {idx}: Invalid transport type '{remote_type}'")
sys.exit(1)
print(f"✓ Remote {idx}: {remote_type} -> {url}")
print('✓ server.json validation passed (hybrid deployment)')
"""
)
)
PY
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}},value=${{ steps.version-check.outputs.current_version }},enable=${{ github.ref == 'refs/heads/main' && steps.version-check.outputs.version_changed == 'true' }}
type=semver,pattern=v{{version}},value=${{ steps.version-check.outputs.current_version }},enable=${{ github.ref == 'refs/heads/main' && steps.version-check.outputs.version_changed == 'true' }}
type=raw,value=latest,enable={{is_default_branch}}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
push: true
platforms: linux/amd64,linux/arm64
file: ./Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: |
${{ steps.meta.outputs.labels }}
io.modelcontextprotocol.server.name=io.github.CodeAlive-AI/codealive-mcp
cache-from: type=gha
cache-to: type=gha
- name: Create git tag
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag "v${{ steps.version-check.outputs.current_version }}"
git push origin "v${{ steps.version-check.outputs.current_version }}"
- name: Install MCP Publisher CLI
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
run: |
curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.1.0/mcp-publisher_1.1.0_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
chmod +x mcp-publisher
- name: Login to MCP Registry (GitHub OIDC)
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
run: |
./mcp-publisher login github-oidc
- name: Publish to MCP Registry
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
run: |
./mcp-publisher publish
- name: Create GitHub Release
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ steps.version-check.outputs.current_version }}
name: CodeAlive MCP v${{ steps.version-check.outputs.current_version }}
body: |
## CodeAlive MCP Server v${{ steps.version-check.outputs.current_version }}
### 🚀 Hybrid Deployment Options
**Docker Container (Local)**
```bash
docker run --rm -i -e CODEALIVE_API_KEY=your-key ghcr.io/codealive-ai/codealive-mcp:v${{ steps.version-check.outputs.current_version }}
```
**MCP Registry**
```json
{
"name": "io.github.codealive-ai/codealive-mcp",
"transport": {
"type": "stdio",
"command": "docker",
"args": ["run", "--rm", "-i", "-e", "CODEALIVE_API_KEY=YOUR_API_KEY_HERE", "ghcr.io/codealive-ai/codealive-mcp:v${{ steps.version-check.outputs.current_version }}"]
}
}
```
**Remote HTTP (Zero Setup)**
```json
{
"transport": {
"type": "http",
"url": "https://mcp.codealive.ai/api"
},
"headers": {
"Authorization": "Bearer your-codealive-api-key"
}
}
```
draft: false
prerelease: false